?

Log in

No account? Create an account
Eyes

Type-casting spells

I am writing this primarily as a future reference, but also in case some of you might have wondered about the same thing, which is: dynamic_cast, static_cast, reinterpret_cast, and const_cast — what are the differences?


const_cast

This cast operator is different from the other three, and is actually easier to explain, so I will do it now.  Put simply, const_cast modifies const-ness and/or volatile-ity (referred to as “cv-qualification” in the C++ standard) of a pointer.

For example, let's say we have a pointer pt to a const object of type T, that is:

T const *pt;

And let's say we wanted to modify a member named x in that object, that is, something like this:

pt->x = 0;

Well, that was an error.  Because the target object is declared const!  It would have worked had pt been declared as of the type T *, not T const *.

In most cases where const is used, it is used because the API author wanted to enforce some data integrity.  So it is usually a BAD™ idea to override that const-ness.  However, if you are really, really sure it is what you want to do (and there shall be no undesirable side effect of doing so), you can override it, and that is when const_cast comes into use: Instead of “pt->x = 0;”, you say “const_cast<C *>(pt)->x = 0;”.


dynamic_cast/static_cast versus reinterpret_cast

Assume that there are three classes, X, Y and Z, and that Z is derived from X and Y.  Then, in memory, an object of type Z has its genuine (non-inherited) members as well as sub-objects of type X and Y within it, which may be laid out like this (the address space grows from left to right):

the Z object as a whole
the X sub-objectthe Y sub-objectthe genuine portion
AX
AZ
AYAZG

Let's say we have a pointer to the instance of Z illustrated above, that is, “Z *pz = new Z;”.  Then pz will point to the address AZ above.  Now let's say we want to have another pointer to an instance of Y, that is, “Y *py;”.  We want py to point to the Y sub-object within the Z object *pz.

If we were to do all the point arithmetic by ourselves, in order to calculate the value for py, we would have to:

  1. First, take the value of pz,
  2. Then add the number of bytes taken up by the X sub-object (that is, AYAX above) to it.

This is tedious.  Especially if we have to do this over and over.  Fortunately, C++ takes care of this for us by adjusting the offset automatically, and it become as simple as saying:

py = dynamic_cast<Y *>(pz);

Or:

py = static_cast<Y *>(pz);

Or even simpler:

py = pz;

Then what about reinterpret_castreinterpret_cast bypasses the offset calculation explained above, and simply copies the numeric address value, bit-by-bit.  So, if we said:

py = reinterpret_cast<Y *>(pz);

Then py would have the same numeric address value as pz, that is, AZ in the diagram above.  Of course, it is not an instance of Y which is laid out at that address, so using reinterpret_cast in this case is probably a logical error.  However, if you are coding some low-level stuff and wanted a bit-by-bit copy of pointer values, just in different types, reinterpret_cast is the way to go.


dynamic_cast versus static_cast

Let's say we are trying to declare a function that takes an instance of either Z or its base class Y as the first argument, and a bool-ean flag which specifies whether the object that the first argument points to is of the base class Y.  In other words, the goal here is to declare the function in such a way that the following code would work:

Y *py = new Y; f(py, true); Z *pz = new Z; f(pz, false);

Well, we could say:

void f(Y *py, bool is_a_Y) { if (is_a_Y) { // Do whatever with py here. } else { Z *pz = py; // Do whatever with pz here. } }

But we have a problem.  C++ does not allow converting py to pz, and the compiler emits an error.  But why?  Can the compiler not simply subtract the number of bytes taken up by the X sub-object (that is, AYAX in the diagram above) from py and use it?”  The answer is: It is dangerous.  Because *py is a stand-alone object of type Y, one should not touch the storage space adjacent to *py, which would have been the X sub-object or the genuine portion of Z, had *py been a part of a larger object of type Z.  So, if C++ calculated pz from py and subsequently passed it to some function which in turn accepted Z * pointer and used it to access non-Y portion (such as the X sub-object or the genuine part of Z), then bam, the function would end up accessing the adjacent storage space which should be left alone.  Yes, dangerous.  And this is why C++ does not allow such implicit conversions.

But we know what we are doing.  We explicitly required the user of our function to specify the type via the second argument to our function, so C++ would better stay back and not get in our way.  And here come dynamic_cast and static_cast into play again:

Z *pz = dynamic_cast<Z *>(py);

Or:

Z *pz = static_cast<Z *>(py);

Now the difference:

  • dynamic_cast is paranoid.  It inspects the object its argument (py in this case) points to, in order to make sure the object is really of the requested type (Z in this case).  If not, it yields a NULL pointer instead of the adjusted pointer.

  • static_cast is not paranoid, but it assumes that the programmer already made sure the argument really points to an object of the requested type, and just performs the pointer adjustment as necessary.  It is therefore more efficient than dynamic_cast.

In our case, we already made sure py points to (an Y sub-object of) a Z object, so we do not need the extra paranoid check dynamic_cast provides, so it suffices—and is more efficient too—to static_cast.

Alternatively, using dynamic_cast we can eliminate the second parameter of f():

void f(Y *py) { Z *pz = dynamic_cast<Z *>(py); if (py != NULL) { // dynamic_cast failed, so py must not be pointing to a Z object. // Do whatever with the original py here. } else { // dynamic_cast succeeded, so py must be pointing to a Z object. // Do whatever with the pz here. } }

Then we can use the function like this:

Y *py = new Y; f(py); Z *pz = new Z; f(pz);

This is much more elegant.  However, it comes at a cost: In order for the run-time inspection of dynamic_cast to work, the pointer argument must be of a polymorphic type, i.e. it must have at least one virtual method.  Otherwise, the dynamic_cast will result in a compile-time error.  In our case for example, the class Y must be polymorphic, because we used a Y * pointer, py, as the argument of dynamic_cast above.

Tags:

Comments

Off topic

Your subject line reminds me of your wizard t-shirt.

Re: Off topic

LOL I still whore out that shirt XD