If you only want a guide to casting in C++, skip to the end of the article. If you want also a bit of technicalities for a better understanding of casting in C++, skip to the one but last section. If you want also a bit of my mind, read the article whole.
What is casting?
The C++ language provides that if a class is derived from a base class containing virtual functions, a pointer to that base class type can be used to call the implementations of the virtual functions residing in the derived class object. A class containing virtual functions is sometimes called a “polymorphic class”.
C++ has completely different needs on type-casting than C. C++ introduced classes and strong type checking; most of C++ functions do not accept void* arguments (if they do, it’s typically sign of bad interface design).
Yet, many if not most of programmers still use the old C-style casting. “The good ol’ ((some_type*) pointer) cast does the same job!”, they say.
And there is no wonder – the C-style does “work” every time, it does not need much of thinking on the programmer’s side, and it’s cheap on typing. To be honest, some 75% of code I write contains C-style casts. But there sure is a big difference between C code (even if written as part of C++ program) and C++ code.
On the other hand, C++’s casts need at first some thinking on programmer’s side, which sure is an activity many of us want to avoid, right? We have enough trouble keeping the big and small picture in head, specifications, design, compatibility, portability, or any other -bility we need, and we need to concentrate to make as few mistakes as possible. But exactly the last point is the point that should make us use the C++’s casts – fewer errors!
What’s more, C++’s casts often allow us to find errors not only in code, but also in our design or in technique at hand.
Bit of technicalities
The expression static_cast<type-id>(expression) converts expression to the type of type-id based solely on the types present in the expression. No run-time type check is made to ensure the safety of the conversion.
In other words, static_cast does pretty much the same job as the C-style cast, expect it makes sure the expression makes sense in general. What’s its advantage then, you ask? It’s so much easier to find it in the source code, which you have to admit to be a huge advantage, if you at least once tried to find casts in a code that uses solely C-style casts.
This is the cast that separates C++ casts from C casts.
The expression dynamic_cast<type-id>(expression) converts the operand expression to an object of type type-id. The type-id must be a pointer or a reference to a previously defined class type or a “pointer to void”. The type of expression must be a pointer if type-id is a pointer, or an l-value if type-id is a reference.
The main hook of dynamic_cast is that it can only be used on objects of polymorphic classes. I.e. Objects of classes that contain virtual function (including virtual destructor). Also, you have to make sure that RTTI (run-time type checking) is enabled in your compiler.
In these cases, dynamic_cast has to be used. Pointer to base class may differ from pointer to the derived class in these cases (depending on implementation of compiler, since this is not defined in standard)!
dynamic_cast returns NULL pointer if the conversion cannot be done.
If you pass void* as type-id to dynamic_cast, a run-time check is made to determine the actual type of expression. The result is a pointer to the complete object pointed to by expression.
There are more strings attached, thanks to multiple inheritance etc., which can be found in e.g. MSDN library.
The reinterpret_cast operator allows any pointer to be converted into any other pointer type. It also allows any integral type to be converted into any pointer type and vice versa. Misuse of the reinterpret_cast operator can easily be unsafe. Unless the desired conversion is inherently low-level, you should use one of the other cast operators.
In other words, you can convert int* to std::string* without any problems with reinterpret_cast. Well, you should consider “without any problems” to be in huge quotes, since such conversion is typically nonsense.
Unless the conversion is low-level, like int* to char*, use of reinterpret_cast typically points out flaws in (interface) design.
Yet, reinterpret_cast can’t do what const_cast can do…
The const_cast operator can be used to remove the const, volatile, and __unaligned attribute(s) from a class.
A pointer to any object type or a pointer to a data member can be explicitly converted to a type that is identical except for the const, volatile, and __unaligned qualifiers. For pointers and references, the result will refer to the original object. For pointers to data members, the result will refer to the same member as the original (uncast) pointer to data member. Depending on the type of the referenced object, a write operation through the resulting pointer, reference, or pointer to data member might produce undefined behavior.
This kind of exotic cast has a simple meaning – you can “legally” write to const objects, out-of-sync write to volatile objects. __unaligned is a qualifier that tells the compiler that accesses to memory through an l-value with this qualifier on it are not known to be on a properly aligned boundary, so the compiler needs to generate somewhat longer code to avoid getting an alignment trap at runtime.
Few tips on casting
You should use C++’s casting instead of C-style casting (at least) whenever you are writing C++ code (as opposed to a C code in a C++ program):
- Errors in casting are among the hardest to discover.
- C-style casting is hard to find in code (while simple search for “_cast” in code will find all C++ casts).
- “Unnecessary casting that is necessary” may often point out flaws in design for you.
This is often not very visible with C-style casting.
There are 4 casting operators in C++:
- dynamic_cast – used for conversion of polymorphic types.
This is a true C++ cast operator, used for moving pointer inside of class hierarchy. The validity of the cast is decided at run-time using RTTI, and NULL pointer is returned if the conversion cannot be done.
- static_cast – used for conversion of non-polymorphic types.
Cast operator for all the “other valid” casts – casts inside of class hierarchy of non-polymorphic types.
There are no steps taken to decide validity of the cast, except of the “in the same hierarchy” check of expression (so called “possibly valid” cast, it’s up to you to ensure the safety).
- const_cast – used to remove the const, volatile, and __unaligned attributes.
Dangerous cast, that allows you to modify const data, or access the volatile data “out-of-order”. This kind of cast mostly does not lead to program crash, but often leads to incorrect results.
Typically use of const_cast shows flaws in class design (modifying const members!) or overall design problems.
- reinterpret_cast – used for simple reinterpretation of bits.
The most dangerous cast, that allows you to turn water into wine. 😉 Mind you, you might not be able to drink it afterwards! reinterpret_cast just takes whatever you put in, and converts it to whatever else you want.
Except of low-level casts, use of reinterpret_cast is a big red flag!
As you can see, there is really only 1 real cast operator – dynamic_cast – that does any checks on validity of result. Yet, there had to be 4 cast operators introduced to cover the whole scope of what C-style casting can be used for.
Next time you need to cast a pointer, stop for a moment and think – is it necessary (if so, why), is it correct? And find C++ cast operator that fits the case in hand. Then look at your program and think, why it has to be the cast operator you chose, and not some other, possibly stronger.