I've been writing C and C++ code for almost twenty years, but there's one aspect of these languages that I've never really understood. I've obviously used regular casts i.e.
MyClass *m = (MyClass *)ptr;
all over the place, but there seem to be two other types of casts, and I don't know the difference. What's the difference between the following lines of code?
MyClass *m = (MyClass *)ptr; MyClass *m = static_cast<MyClass *>(ptr); MyClass *m = dynamic_cast<MyClass *>(ptr);
static_cast − This is used for the normal/ordinary type conversion. This is also the cast responsible for implicit type coersion and can also be called explicitly. You should use it in cases like converting float to int, char to int, etc. dynamic_cast −This cast is used for handling polymorphism.
While typeid + static_cast is faster than dynamic_cast , not having to switch on the runtime type of the object is faster than any of them.
dynamic_cast is exclusively used for handling polymorphism. You can cast a pointer or reference to any polymorphic type to any other class type (a polymorphic type has at least one virtual function, declared or inherited).
In short: static_cast<>() gives you a compile time checking ability, C-Style cast doesn't. static_cast<>() is more readable and can be spotted easily anywhere inside a C++ source code, C_Style cast is'nt. Intentions are conveyed much better using C++ casts.
void func(void *data) { // Conversion from MyClass* -> void* is implicit MyClass *c = static_cast<MyClass*>(data); ... } int main() { MyClass c; start_thread(&func, &c) // func(&c) will be called .join(); }
In this example, you know that you passed a MyClass
object, and thus there isn't any need for a runtime check to ensure this.
if (JumpStm *j = dynamic_cast<JumpStm*>(&stm)) { ... } else if (ExprStm *e = dynamic_cast<ExprStm*>(&stm)) { ... }
You cannot use dynamic_cast
if you downcast (cast to a derived class) and the argument type is not polymorphic. For example, the following code is not valid, because Base
doesn't contain any virtual function:
struct Base { }; struct Derived : Base { }; int main() { Derived d; Base *b = &d; dynamic_cast<Derived*>(b); // Invalid }
An "up-cast" (cast to the base class) is always valid with both static_cast
and dynamic_cast
, and also without any cast, as an "up-cast" is an implicit conversion (assuming the base class is accessible, i.e. it's a public
inheritance).
These casts are also called C-style cast. A C-style cast is basically identical to trying out a range of sequences of C++ casts, and taking the first C++ cast that works, without ever considering dynamic_cast
. Needless to say, this is much more powerful as it combines all of const_cast
, static_cast
and reinterpret_cast
, but it's also unsafe, because it does not use dynamic_cast
.
In addition, C-style casts not only allow you to do this, but they also allow you to safely cast to a private base-class, while the "equivalent" static_cast
sequence would give you a compile-time error for that.
Some people prefer C-style casts because of their brevity. I use them for numeric casts only, and use the appropriate C++ casts when user defined types are involved, as they provide stricter checking.
char c = 10; // 1 byte int *p = (int*)&c; // 4 bytes
Since this results in a 4-byte pointer pointing to 1 byte of allocated memory, writing to this pointer will either cause a run-time error or will overwrite some adjacent memory.
*p = 5; // run-time error: stack corruption
In contrast to the C-style cast, the static cast will allow the compiler to check that the pointer and pointee data types are compatible, which allows the programmer to catch this incorrect pointer assignment during compilation.
int *q = static_cast<int*>(&c); // compile-time error
To force the pointer conversion, in the same way as the C-style cast does in the background, the reinterpret cast would be used instead.
int *r = reinterpret_cast<int*>(&c); // forced conversion
This cast handles conversions between certain unrelated types, such as from one pointer type to another incompatible pointer type. It will simply perform a binary copy of the data without altering the underlying bit pattern. Note that the result of such a low-level operation is system-specific and therefore not portable. It should be used with caution if it cannot be avoided altogether.
This one is only used to convert object pointers and object references into other pointer or reference types in the inheritance hierarchy. It is the only cast that makes sure that the object pointed to can be converted, by performing a run-time check that the pointer refers to a complete object of the destination type. For this run-time check to be possible the object must be polymorphic. That is, the class must define or inherit at least one virtual function. This is because the compiler will only generate the needed run-time type information for such objects.
Dynamic cast examples
In the example below, a MyChild
pointer is converted into a MyBase
pointer using a dynamic cast. This derived-to-base conversion succeeds, because the Child object includes a complete Base object.
class MyBase { public: virtual void test() {} }; class MyChild : public MyBase {}; int main() { MyChild *child = new MyChild(); MyBase *base = dynamic_cast<MyBase*>(child); // ok }
The next example attempts to convert a MyBase
pointer to a MyChild
pointer. Since the Base object does not contain a complete Child object this pointer conversion will fail. To indicate this, the dynamic cast returns a null pointer. This gives a convenient way to check whether or not a conversion has succeeded during run-time.
MyBase *base = new MyBase(); MyChild *child = dynamic_cast<MyChild*>(base); if (child == 0) std::cout << "Null pointer returned";
If a reference is converted instead of a pointer, the dynamic cast will then fail by throwing a bad_cast
exception. This needs to be handled using a try-catch
statement.
#include <exception> // … try { MyChild &child = dynamic_cast<MyChild&>(*base); } catch(std::bad_cast &e) { std::cout << e.what(); // bad dynamic_cast }
The advantage of using a dynamic cast is that it allows the programmer to check whether or not a conversion has succeeded during run-time. The disadvantage is that there is a performance overhead associated with doing this check. For this reason using a static cast would have been preferable in the first example, because a derived-to-base conversion will never fail.
MyBase *base = static_cast<MyBase*>(child); // ok
However, in the second example the conversion may either succeed or fail. It will fail if the MyBase
object contains a MyBase
instance and it will succeed if it contains a MyChild
instance. In some situations this may not be known until run-time. When this is the case dynamic cast is a better choice than static cast.
// Succeeds for a MyChild object MyChild *child = dynamic_cast<MyChild*>(base);
If the base-to-derived conversion had been performed using a static cast instead of a dynamic cast the conversion would not have failed. It would have returned a pointer that referred to an incomplete object. Dereferencing such a pointer can lead to run-time errors.
// Allowed, but invalid MyChild *child = static_cast<MyChild*>(base); // Incomplete MyChild object dereferenced (*child);
This one is primarily used to add or remove the const
modifier of a variable.
const int myConst = 5; int *nonConst = const_cast<int*>(&myConst); // removes const
Although const
cast allows the value of a constant to be changed, doing so is still invalid code that may cause a run-time error. This could occur for example if the constant was located in a section of read-only memory.
*nonConst = 10; // potential run-time error
const
cast is instead used mainly when there is a function that takes a non-constant pointer argument, even though it does not modify the pointee.
void print(int *p) { std::cout << *p; }
The function can then be passed a constant variable by using a const
cast.
print(&myConst); // error: cannot convert // const int* to int* print(nonConst); // allowed
Source and More Explanations
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With