C++ Explicit Casts

Casting is one of the most troublesome operations in C/C++ as when you use casts you are directing the compiler to trust you and to forget about type checking. Casts should be used as infrequently as possible and only when there is no alternative. We have two main terms for casting, upcasting and downcasting. Upcasting is casting from a derived class to one of its base classes and downcasting is casting from a base class to one of its derived classes. Remember that the inheritance tree branches down with the base class at the top. We also have the lesser known cross-casting for casting a class to a sibling class.

C++ introduces new explicit casts that identify the rationale behind the cast, clearly identifies that a cast is taking place and confirms type-safe conversions. These casts are:

Here is an example of all four casts:

 1 
 2  // Start reading from main() function to help make it easier to understand
 3  #include <iostream>
 4  using namespace std;
 5 
 6  // Demo classes with a virtual base class
 7  class Account {
 8       public:
 9              float balance; // for demonstration only!
10              virtual ~Account(){}
11  };
12  class Current: public Account {};
13  class Test { public: float x, y;};
14 
15  // Demo Function
16  void someFunction(int& c)
17  {
18    c++;   // c can be modified
19    cout << "c has value " << c << endl; //will output 11
20  }
21 
22  int main()
23  {
24     float f = 200.6f;
25      // narrowing conversion, but we have notified the compiler
26     int x = static_cast<int>(f); 1
27     cout << x << endl;
28 
29     Account *a = new Current; //upcast - cast derived to base
30     //type-safe downcast - cast base to derived 
31     Current *c = dynamic_cast<Current*>(a); 2
32 
33     const int i = 10;     // note i is const
34     //someFunction(i);    // is an error as someFunction
35                           // could modify the value of i
36     someFunction(*const_cast<int*>(&i));3
37         // Allow i be passed to the function but it will still remain at 10.
38     cout << "i has value " << i << endl; // will still output 10
39 
40     a = new Account;
41     a->balance = 1000;
42      //convert account address to long
43     long addr = reinterpret_cast<long>(a);4
44     
45     // safe to convert long address into an Account
46     Account* b = reinterpret_cast<Account*>(addr);5
47     cout << "The balance of b is " << b->balance << endl;
48 
49     // could convert to any class regardless of 
50     // inheritance - ok this time! (not safe)
51     Current* cur = reinterpret_cast<Current*>(addr);6
52     cout << "The balance of cur is " << cur->balance << endl;
53 
54     // works, but not definitely not safe this time!
55     Test* test = reinterpret_cast<Test*>(addr);7
56     cout << "The value of the float x is " << test->x <<
57          " and float y is " << test->y << endl;
58  }
59 
The output of this code is:
 C:\My Documents\My Teaching\OOP Notes\EE553_Output\cpp>casting
 200 12
 c has value 11
 i has value 103
 The balance of b is 100045
 The balance of cur is 1000 6
 The value of the float x is 1000 and float y is 5.98875e-39 7
1

The static_cast here demonstrates the most common casting operation that we have previously performed using the C-style cast. In this case, we are simply converting a float into an int and notifying the compiler of our intention - Use a static_cast for all such conversions. In this example 200.6 will be converted to 200 as outputted.

2

The dynamic_cast here demonstrates downcasting an Account object to a CurrentAccount object. A dynamic_cast must be used when there are virtual base classes (discussed later). In this example there is no output.

3

The const_cast here takes the const i and allows it to be passed by reference to a function someFunction() that takes a non-constant parameter by reference. In this function we can treat the value just like a non-constant, but it has no impact on the original constant. As shown in the line directly above we would not have been allowed to pass this constant without the const_cast.

4

The reinterpret_cast here converts the address of the Account object into a long.

5

The reinterpret_cast is used again, this time to convert a long into a pointer to an Account object. This is dangerous as it is unchecked, but is perfectly appropriate in this case. The output shows a balance of 1000, so it worked correctly.

6

The reinterpret_cast is then used to convert the same long pointer into a Current object. Again it is fine in this case, but unchecked.

7

The reinterpret_cast is finally used to do a very unsafe operation of converting the long into the address of a random Test object. This works perfectly, but it is totally inappropriate as you can see in the output the int value of 1000 has been converted to a float x, but the next block of random memory has also been assigned to the float y. Modifying y would cause unpredictable results, possibly crashing the application.