MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

Explicit cast syntax

Whenever you use a cast, you’re breaking the type system. [62] You’re telling the compiler that even though you know an object is a certain type, you’re going to pretend it is a different type. This is an inherently dangerous activity, and a clear source of errors.

Unfortunately, each cast is different: the name of the pretender type surrounded by parentheses. So if you are given a piece of code that isn’t working correctly and you know you want to examine all casts to see if they’re the source of the errors, how can you guarantee that you find all the casts? In a C program, you can’t. For one thing, the C compiler doesn’t always require a cast (it’s possible to assign dissimilar types through a void pointer without being forced to use a cast), and the casts all look different, so you can’t know if you’ve searched for every one.

To solve this problem, C++ provides a consistent casting syntax using four reserved words: dynamic_cast (the subject of the first part of this chapter), const_cast, static_cast, and reinterpret_cast. This window of opportunity opened up when the need for dynamic_cast arose – the meaning of the existing cast syntax was already far too overloaded to support any additional functionality.

By using these casts instead of the (newtype) syntax, you can easily search for all the casts in any program. To support existing code, most compilers have various levels of error/warning generation that can be turned on and off. But if you turn on full errors for the explicit cast syntax, you can be guaranteed that you’ll find all the places in your project where casts occur, which will make bug-hunting much easier.

The following table describes the different forms of casting:

static_cast

For “well-behaved” and “reasonably well-behaved” casts, including things you might now do without a cast (e.g., an upcast or automatic type conversion).

const_cast

To cast away const and/or volatile.

dynamic_cast

For type-safe downcasting (described earlier in the chapter).

reinterpret_cast

To cast to a completely different meaning. The key is that you’ll need to cast back to the original type to use it safely. The type you cast to is typically used only for bit twiddling or some other mysterious purpose. This is the most dangerous of all the casts.

The three explicit casts will be described more completely in the following sections.

static_cast

A static_cast is used for all conversions that are well-defined. These include “safe” conversions that the compiler would allow you to do without a cast and less-safe conversions that are nonetheless well-defined. The types of conversions covered by static_cast include typical castless conversions, narrowing (information-losing) conversions, forcing a conversion from a void*, implicit type conversions, and static navigation of class hierarchies:

//: C24:Statcast.cpp
// Examples of static_cast

class Base { /* ... */ };
class Derived : public Base {
public:
  // ...
  // Automatic type conversion:
  operator int() { return 1; }
};

void func(int) {}

class Other {};

int main() {
  int i = 0x7fff; // Max pos value = 32767
  long l;
  float f;
  // (1) typical castless conversions:
  l = i;
  f = i;
  // Also works:
  l = static_cast<long>(i);
  f = static_cast<float>(i);

  // (2) narrowing conversions:
  i = l; // May lose digits
  i = f; // May lose info
  // Says "I know," eliminates warnings:
  i = static_cast<int>(l);
  i = static_cast<int>(f);
  char c = static_cast<char>(i);

  // (3) forcing a conversion from void* :
  void* vp = &i;
  // Old way produces a dangerous conversion:
  float* fp = (float*)vp;
  // The new way is equally dangerous:
  fp = static_cast<float*>(vp);

  // (4) implicit type conversions, normally
  // Performed by the compiler:
  Derived d;
  Base* bp = &d; // Upcast: normal and OK
  bp = static_cast<Base*>(&d); // More explicit
  int x = d; // Automatic type conversion
  x = static_cast<int>(d); // More explicit
  func(d); // Automatic type conversion
  func(static_cast<int>(d)); // More explicit

  // (5) Static Navigation of class hierarchies:
  Derived* dp = static_cast<Derived*>(bp);
  // ONLY an efficiency hack. dynamic_cast is
  // Always safer. However:
  // Other* op = static_cast<Other*>(bp);
  // Conveniently gives an error message, while
  Other* op2 = (Other*)bp;
  // Does not.
} ///:~ 

In Section (1), you see the kinds of conversions you’re used to doing in C, with or without a cast. Promoting from an int to a long or float is not a problem because the latter can always hold every value that an int can contain. Although it’s unnecessary, you can use static_cast to highlight these promotions.

Converting back the other way is shown in (2). Here, you can lose data because an int is not as “wide” as a long or a float – it won’t hold numbers of the same size. Thus these are called “narrowing conversions.” The compiler will still perform these, but will often give you a warning. You can eliminate this warning and indicate that you really did mean it using a cast.

Assigning from a void* is not allowed without a cast in C++ (unlike C), as seen in (3). This is dangerous and requires that a programmer know what he’s doing. The static_cast, at least, is easier to locate than the old standard cast when you’re hunting for bugs.

Section (4) shows the kinds of implicit type conversions that are normally performed automatically by the compiler. These are automatic and require no casting, but again static_cast highlights the action in case you want to make it clear what’s happening or hunt for it later.

If a class hierarchy has no virtual functions or if you have other information that allows you to safely downcast, it’s slightly faster to do the downcast statically than with dynamic_cast, as shown in (5). In addition, static_cast won’t allow you to cast out of the hierarchy, as the traditional cast will, so it’s safer. However, statically navigating class hierarchies is always risky and you should use dynamic_cast unless you have a special situation.

const_cast

If you want to convert from a const to a non const or from a volatile to a non volatile, you use const_cast. This is the only conversion allowed with const_cast; if any other conversion is involved it must be done separately or you’ll get a compile-time error.

//: C24:Constcst.cpp
// Const casts

int main() {
  const int i = 0;
  int* j = (int*)&i; // Deprecated form
  j  = const_cast<int*>(&i); // Preferred
  // Can't do simultaneous additional casting:
//! long* l = const_cast<long*>(&i); // Error
  volatile int k = 0;
  int* u = const_cast<int*>(&k);
}

class X {
  int i;
// mutable int i; // A better approach
public:
  void f() const {
    // Casting away const-ness:
    (const_cast<X*>(this))->i = 1;
  }
}; ///:~ 

If you take the address of a const object, you produce a pointer to a const, and this cannot be assigned to a non const pointer without a cast. The old-style cast will accomplish this, but the const_cast is the appropriate one to use. The same holds true for volatile.

If you want to change a class member inside a const member function, the traditional approach is to cast away constness by saying (X*)this. You can still cast away constness using the better const_cast, but a superior approach is to make that particular data member mutable, so it’s clear in the class definition, and not hidden away in the member function definitions, that the member may change in a const member function.

reinterpret_cast

This is the least safe of the casting mechanisms, and the one most likely to point to bugs. At the very least, your compiler should contain switches to allow you to force the use of const_cast and reinterpret_cast, which will locate the most unsafe of the casts.

A reinterpret_cast pretends that an object is just a bit pattern that can be treated (for some dark purpose) as if it were an entirely different type of object. This is the low-level bit twiddling that C is notorious for. You’ll virtually always need to reinterpret_cast back to the original type before doing anything else with it.

//: C24:Reinterp.cpp
// Reinterpret_cast
// Example depends on VPTR location,
// Which may differ between compilers.
#include <cstring>
#include <fstream>
using namespace std;
ofstream out("reinterp.out");

class X {
  enum { sz = 5 };
  int a[sz];
public:
  X() { memset(a, 0, sz * sizeof(int)); }
  virtual void f() {}
  // Size of all the data members:
  int membsize() { return sizeof(a); }
  friend ostream&
    operator<<(ostream& os, const X& x) {
      for(int i = 0; i < sz; i++)
        os << x.a[i] << ' ';
      return os;
  }
  virtual ~X() {}
};

int main() {
  X x;
  out << x << endl; // Initialized to zeroes
  int* xp = reinterpret_cast<int*>(&x);
  xp[1] = 47;
  out << x << endl; // Oops!

  X x2;
  const int vptr_size= sizeof(X) - x2.membsize();
  long l = reinterpret_cast<long>(&x2);
  // *IF* the VPTR is first in the object:
  l += vptr_size; // Move past VPTR
  xp = reinterpret_cast<int*>(l);
  xp[1] = 47;
  out << x2 << endl;
} ///:~ 

The class X contains some data and a virtual member function. In main( ), an X object is printed out to show that it gets initialized to zero, and then its address is cast to an int* using a reinterpret_cast. Pretending it’s an int*, the object is indexed into as if it were an array and (in theory) element one is set to 47. But here’s the output: [63]

0 0 0 0 0 
47 0 0 0 0  
Clearly, it’s not safe to assume that the data in the object begins at the starting address of the object. In fact, this compiler puts the VPTR at the beginning of the object, so if xp[0] had been selected instead of xp[1], it would have trashed the VPTR.

To fix the problem, the size of the VPTR is calculated by subtracting the size of the data members from the size of the object. Then the address of the object is cast (again, with reinterpret_cast) to a long, and the starting address of the actual data is established, assuming the VPTR is placed at the beginning of the object. The resulting number is cast back to an int* and the indexing now produces the desired result:

0 47 0 0 0

Of course, this is inadvisable and nonportable programming. That’s the kind of thing that a reinterpret_cast indicates, but it’s available when you decide you have to use it.


[62] See Josée Lajoie , “The new cast notation and the bool data type,” C++ Report, September, 1994 pp. 46-51.

[63] For this particular compiler. Yours will probably be different.

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru