Casts
From CS371p
Contents |
C++ Casts
C++ provides us with four different types of casts for performing various type conversions. Let T denote a typename and obj denote an instance of some type. Following are the four C++ casts
- static_cast<T>(obj) Used for reasonably straightforward casts
- reinterpret_cast<T>(obj) Used to convert between unrelated types.
- const_cast<T>(obj) Used to change const/volatile nature of obj
- dynamic_cast<T>(obj) Used to navigate the class inheritance hierarchies using run time type identification
Why do we need different C++ casts instead of C cast?
The C style cast still works in C++. i.e. C cast syntax is legal when used in C++ and converts type T1 to type T2. Nevertheless the use of old C style cast is deprecated in C++. Some of the important reasons are as follows:
- Casts are basically needed in different situations to produce different effects. For example when casting a double into int, we do not need to change the constness of double. In general we may want to change the meaning of a variable for some part of the code, we may want to remove the constness of a variable, we may want to navigate the class inheritance hierarchy etc. Such distinct needs are not treated elegantly in C casts. C++ gives us different casts to effectively handle different needs.
- C casts are error prone. A small change in the declaration of variables can cause big difference in meaning. For example consider this code:
class Derived : public Base {....}
void foo(Derived* ptrD)
{
((Base*)prtD)->bar(); /*bar is a member method of class Base*/
}
Now change the class Derived to:
class Derived{....} /*no more derives from Base*/
Here the meaning of (Base*)prtD is entirely different. This change in the meaning becomes much more explicit if we use C++ casts.
- Syntax of C casts: C syntax for casting obj of type T2 to type T1 is:
(T1)obj
It is very difficult to spot all the cast operations in the code using tools like grep. Expressions involving this pattern are so common that using grep becomes very inconvenient.
- C++ casts are designed in such a way that you can perform all the operations that you can perform using C casts.
Details of different C++ casts:
static_cast<T>obj
Typically you need the conversions like double to int. This is not necessarily a safe conversion (some data is lost). But your program logic may guarantee the correctness of such a cast. In such a situation use static_cast. Another typical situation where you can use static_cast is casting Base* to Derived* (a better way is to use dynamic_cast). Again this may not be a safe thing in general but your logic may guarantee the correctness. Note that static_cast will not use RTTI (in contrast to dynamic_cast). So use this cast only when you are sure about the correctness of the conversion without relying on the RTTI. In general if conversion T1 to T2 happens implicitly then, use static_cast for conversion T2 to T1. static_cast respects the constness (and volatileness) of data and the access to data (private/public/protected). static_cast is also used for conversions between T* and void*. For doing pointer or reference conversions both the types must be completely defined before using the cast. This is one major difference between static_cast and C style cast. For example:
class A; /*declaration only*/
class B; /*declaration only*/
void foo(A* ptrA)
{
B* ptrB;
ptrB = (B*)ptrA; /*legal C cast*/
//ptrB = static_cast<B*>ptrA; /*won’t compile*/
}
Among all the C++ casts, ststic_cast is semantically closest to the C style cast.
reinterpret_cast<T>obj
This is typically used when doing pointer conversions. More precisely if you need to convert T1* to T2* and T1 and T2 are not as such related to each other then use reinterpret_cast. Again such a conversion may not be safe and the program logic needs to ensure the correctness. Probably you used reinterpret_cast in Allocator project for conversions between sentinels (int*) and actual data (T*). Note that as such (int*) and (T*) are not related. But the program logic indicates that such a conversion is required and can be performed. Note that reinterpret_cast does NOT perform any class hierarchy navigation (it just reinterprets the bits and hence is very dangerous). static_cast performs the class hierarchy navigation without using RTTI. Following example will make this clearer:
class Base {....};
class Derived : public Base {....};
void foo(Base* ptrBase)
{
Derived* ptdD1 = reinterpret_cast<Derived*>(ptrBase); /*probably you don’t want this*/
Derived* ptrD2 = static_cast<Derived*>(ptrBase); /*looks appropriate*/
}
Derived objDerived = new Derived();
foo(objDerived);
Now, ptrD1 still points to the beginning of the object. But prtD2 points to the start of Base portion of the object.
reinterpret_cast respects the constness (and volatileness) of data.
const_cast<T>obj
This is used to modify the constness (or volatileness) of an object.
void foo(T t){....}
const T t;
// foo(t); /*wont compile, t is const*/
foo(const_cast<T>(t)); /*works fine*/
While using const_cast<T>obj, type T and type of the object obj MUST be the same. If not then the compiler will complain. const_cast is useful because it allows you to explicitly modify constness (or volatileness) of the object. So you do not end up with surprises. To guard a programmer against unintentional modification of constness, all the C++ casts except const_cast respect constness of the object. Same is true with volatileness. const_cast can not modify any aspect of the object other than constness (and volatileness). Hence if you need to perform any other modification to the object, you have to use combination of const_cast and other appropriate cast.
dynamic_cast<T>obj
This cast uses RTTI and is very useful to safely navigate the class inheritance hierarchies. You can use dynamic_cast to cast down the hierarchy and only if the cast is proper, you get a non-zero return value. For example:
Class Base {….}
Class Derived1 : public Base {….}
Class Derived2 : public Base {….}
Base* ptrB = new Derived1;
Derived1* ptrD1 = dynamic_cast<Derived1*>ptrB; /*this is correct downcast*/
Derived2* ptrD2 = dynamic_cast<Derived2*>ptrB; /*this is incorrect downcast*/
Here ptrD1 gets correct nonzero value, prtD2 gets zero value. While using these pointers, you can check for nonzero value.
Note that dynamic_cast uses the Virtual Function Pointer Table of the class to determine RTTI. Thus you must have at least one virtual function in your base class in order to make use of dynamic_cast. This can be somewhat expensive operation.
Written by Archis Apte
