Type casting was a powerful addition to C sometimes too powerful. C++ lets you describe the kind of type cast you want with a bit more refinement.
Introduction
One of the least well known features of C++ is its new casting operators. Four have been added during standardization:
dynamic_cast static_cast reinterpret_cast const_cast
Collectively they can be used in place of any C-style cast. Each operator performs a different kind of cast, with a well defined purpose. All casts follow the format:
operator<type required>(expression)
Syntactically the operators look like template functions where the return type is explicitly specified. For example, if static_cast were a function it might be prototyped as:
template<class TO, class FROM> TO static_cast(FROM From) {return (TO)From; }
and used like this :
Derived *d = static_cast<Derived*>(base_pointer);
Using the new cast operators can make your code easier to follow. When examining a C-style cast you often need to know the type of the variable being assigned to, and you also need to have detailed knowledge of the type of the variable you are casting from. For example, if you are downcasting, you need to know if the variable you are casting from actually points to the type you want. While you may possess this knowledge when you write the cast, someone who has never seen your code before may take some time to decipher just what it is your cast is doing. The C++ casting operators allow someone to see exactly what you are intending to do, as all the information is clearly present at the point of the cast.
Dynamic Casts
dynamic_cast is the most interesting of the four casting operators. Given a pointer or a reference to an object of type B, dynamic_cast attempts to convert that object to a pointer or a reference to a type D if D is derived from B. What makes dynamic_cast special is that if D is not derived from B then the cast returns a null pointer. For example:
class Base { public: virtual ~Base() {}; }; class Derived : public Base { }; class Strange { public: virtual ~Strange() {}; }; int main() {Derived derived; Base *base=&derived; Strange strange; if (dynamic_cast<Derived*>(base)) cout << "base is a Derived." << endl; else cout << "base is not " << "a Derived!" << endl; if (dynamic_cast<Derived*>(&strange)) cout << "strange is " << "a Derived." << endl; else cout << "strange is not " << "a Derived!" << endl; return 0; }
In this example you will see the output:
base is a Derived. strange is not a Derived!
The dynamic_cast operator successfully determines that in the first case base is in fact a pointer to an object of type Derived. In the second case, dynamic_cast works out that strange is not a pointer to an object whose type is derived from Derived, and returns a null pointer.
As I mentioned earlier, dynamic_cast can also cast a reference from one type to another. This introduces an interesting scenario. Since a reference must reference an object, we could run into trouble if dynamic_cast returns a null pointer. For example:
Derived1 derived_class; Derived1 &derived_ref = derived_class; SuperDerived &super = dynamic_cast<SuperDerived&> (derived_ref);
dynamic_cast cannot initialize a reference to designate no object, since C++ prohibits this. But it would be incorrect to allow execution to continue any further. The solution to this problem is for dynamic_cast to throw a bad_cast exception whenever a cast from one reference to another fails. Thus, you should write something like:
try {SuperDerived &super = dynamic_cast<SuperDerived&> (derived_ref); // Safe to use super here } catch(bad_cast) {cout << "Oops" << endl; }
Although there is nothing to stop you from allowing a bad_cast exception to propagate up the call chain, in my experience this is a practice best avoided. The exception has no meaning outside the scope of the function which performed the cast. Instead I find it best to catch a bad_cast exception in the function that performed the cast, and to take measures to handle the failure accordingly.
All this functionality does not come without some cost. Type information must be available at run time so that dynamic_cast can determine if a cast is correct. This information is generated by the compiler and placed in the vtable, or table of pointers to virtual functions. This has important implications. You must have at least one virtual function in your class hierarchy for dynamic_cast to work, otherwise you will get a compile time error. This may seem a bit unreasonable at first, but do not despair! It is good practice to make your base class destructor virtual so that any objects allocated on the heap can be successfully deleted through base pointers. By doing this you will allow dynamic_cast to work on your classes. (Besides, if a class has no virtual functions, you probably have no need to apply a dynamic cast anyway.)
There is also the additional run time cost of determining if the class you wish to cast to is available, given the pointer or reference you are supplying. Each call to dynamic_cast will probably result in a call to a runtime library function to determine if the cast is correct. While this is expensive, it is not as expensive, or dangerous, as performing a downcast to an object that is not correctly accessible. For example:
void Process (Base *base) {Derived *d = static_cast <Derived*>(base); // Highly risky, // safe to use d now? } // Process will function OK Process (new Derived); // Death, destruction and mayhem! Process (new Base);
If you need to perform a downcast, always use dynamic_cast. It is the only way to guarantee that you get back a correct value. In a perfect world there should never be a need to perform a downcast. However, in my experience, you will need to perform one sooner or later. If you need to use dynamic_cast to select among various alternatives, then you should cast from one pointer type to another. If you require a cast to succeed and regard it as an error if it does not, then cast a reference and catch the bad_cast exception. Code like this:
Derived *d = dynamic_cast<Derived*>(base); assert(d != 0);
is fine during debugging, but it will not protect you when you compile your code for release and the assertion is removed.
Static Casts
static_cast is aimed at replacing explicit casts. Conceptually, if you can say:
Base *base = derived;
without casting (the compiler can implicitly perform the cast here) then you can use static_cast to do an explicit cast by saying:
Derived *derived = static_cast<Derived*>(base);
static_cast can also be used to cast from one intrinsic type to another (such as int to char) or to invoke user-defined conversion functions.
The ability to perform a downcast may at first appear to conflict with the functionality of dynamic_cast, which performs the same job but with type safety. But dynamic_cast carries out a run-time check to see if a conversion is correct and static_cast does not. The latter therefore incurs no run-time performance penalty. static_cast relies solely on information you supply at compile time. Even though you can use static_cast to carry out a downcast, I strongly suggest you refrain from doing so as it can lead to bugs that are difficult to track down. Always use dynamic_cast to downcast.
reinterpret_cast
While static_cast will allow you to cast from one pointer type to another, it will not allow you to do casts such as int* to Derived*. For this kind of scenario you should use reinterpret_cast:
Base *b = new Base; Derived *d = static_cast<Derived*>(b); // Will return a value, but not necessarily correct long *p; Base *bs = static_cast<Base*>(p); // Compile time error, use reinterpret_cast
reinterpret_cast is used for those times when you have data stored in a variable whose type is completely incompatible with the type of the data you actually want. It allows you to convert from one incompatible pointer type to another as well as converting any intrinsic type (such as int or long) to a pointer and vice versa. For example, you may have a variable of type long which actually holds a pointer to a structure.
This type of scenario is extremely common in GUI-based programming where you have a common callback function that handles many different messages, each of which takes different parameters. For example:
void WindowCallback(WORD Message, LONG Data) {switch(Message) { case CREATE: {WIN_DATA *WinData = reinterpret_cast<WIN_DATA*>(Data); // .. Use WinData } break; case PAINT: {PNT_DATA *PaintData = reinterpret_cast<PNT_DATA*>(Data); // .. Use PaintData } break; } }
You should not use reinterpret_cast to cast from one object pointer to another as it does not navigate the class hierarchy in order to see if the cast is possible. It relies on the static type information you provide the compiler. If you try this:
char *Name = "Hello there" MyClass *Class = reinterpret_cast<MyClass*>(Name);
then the compiler is not going to stop you. You do these casts at your own risk!
reinterpret_cast is almost always used in low-level scenarios, and is usually implementation dependent. Some people frown on its use and claim that it reveals holes in the design of a system. I can't help but feel this is a bit harsh. The practice of converting a pointer to a long and passing the long to a function is very common in low-level C code, as the GUI example above illustrates. If you write C++ code that uses C code you are going to come across this at some point. Even in a purely C++ world, it is unlikely that you will never need to cast between incompatible types. By using the reinterpret_cast operator you can easily highlight these low-level conversions in your code so that other people will see what you are doing.
Const Casts
const_cast is used to remove the const or volatile attribute from a variable. It is the only way to remove the const or volatile attribute from a type. All the other casting operators respect these qualifiers. For the types const X or volatile X, the const_cast operator will cast to the type X. The type specified in the angle brackets must be exactly the same as the type of the expression you are casting from, but without the const or volatile keyword. For example:
void ProcessString(char *Data, int Length) {// Do something with data } int CalculateChecksum(const char *Data, int Lengh) {ProcessString(const_cast<char*>(Data),Length); }
This may strike you as being a bit bogus, and you'd be right to be concerned! If you looked at the prototype for CalculateChecksum you would be led to believe that it does not modify the Data parameter, and yet if you looked at the source for it you would see that internally it was potentially breaking this promise.
The problem here lies in the ProcessString function. Since its Data parameter is not const, CalculateChecksum must cast the argument in order to call the function. Ideally this cast should be avoided by changing ProcessString so that it takes a const char*. However, this is not always possible. It you wrote the function yourself then you are in luck. You can easily change it, providing of course that it really doesn't modify its Data parameter. In my experience, it is relatively common to come across functions that don't modify parameters that are nevertheless not declared as const.
If ProcessString comes from a third-party library to which you do not have the source code then you're out of luck. This is where const_cast comes into its own. Removing the const attribute from a variable so that you can pass it into a function can lead to some incredibly subtle errors if the function you call happens to modify the data you pass in. If you find that you are casting away the const attribute regularly in order to call a function, then you should provide a wrapper function in order to call that function and insert checks to ensure that your data never changes. For example, the above code could be rewritten like this:
void ProcessString(char *Data, int Length) { // Do something with data } void InternalProcessString(char *Data, int Length) { #ifdef DEBUG char *Copy = _str(Data); #endif ProcessString(const_cast<char*>(Data),Length); #ifdef DEBUG // Make sure the data did not change assert(strcmp(Data, Copy) == 0); free(Copy); #endif } int CalculateChecksum(const char *Data, int Lengh) {InternalProcessString(Data,Length); }
(NOTE: A better solution would be to overload ProcessString to take a const char*. The reason I have not done this is to avoid ambiguity in the paragraph below.)
Here the const_cast operator is localized to one place. Also, when you build for debugging, the code makes a run-time check to ensure that ProcessString does not change the data passed in. The assertion will protect us from subtle errors arising if ProcessString changes the data. Even if ProcessString does not modify the data now, it is possible that its implementation could be changed in future so that it does. If ProcessString does modify your data then you can always remove the conditionally included code and pass in a copy of the data, thus completely protecting yourself.
Conclusion
Having read this article you may feel that the new casting operators are not for you, since they require far more typing to accomplish something that you can easily do with old-style C casts. If this is the case, then I ask you to think again. The new casting operators have three important roles that justify their use.
The first role is to document your code more thoroughly. This is extremely important if you work in an environment where many people will use or maintain your code. The new casting operators allow you to show more clearly what format the data currently has and what format you would like it to have. Code like this:
WIN_DATA *WinData = (WIN_DATA*)Data;
tells us nothing about what Data is. It could be some type holding a pointer or it could be a const WIN_DATA* and we are attempting to remove the const attribute. By saying:
WIN_DATA *WinData = reinterpret_cast<WIN_DATA*>(Data);
it is clear that Data is of a type completely unrelated to WIN_DATA but holds a pointer to a WIN_DATA object.
The second role is to trap unsafe downcasts, by using the dynamic_cast operator. With old-style casts, any downcast you made was done at your own risk. If you were lucky, you would discover a bad downcast during development. If you were unlucky, you would find it to be the cause of a crash that one of your clients reported.
The third role is to make you question whether you should cast or not in the first place. (The lengthy names of the new-style casts helps in this regard.) If you are using const_cast all over the place, shouldn't you look to see whether the data should be non-const in the first place? If you are constantly using dynamic_cast to perform a safe downcast in order to access certain functionality, then shouldn't that functionality be in a virtual function accessible through a base-class pointer?
In all three roles, the new casting operators help you write better code.
Additional Reading
Bjarne Stroustrup. The Design and Evolution of C++ (Addison Wesley, 1994).
Stan Lippman. Inside the C++ Object Model (Addison Wesley, 1996).
Sean Batten works for Braid Systems Ltd, a company based in London, England, specializing in messaging software based. He has a BSc(Hons) in Computer Science from Kingston University. He may be reached at [email protected] or at http://www.scoobie.demon.co.uk.