GotW #17

Home Blog Talks Books & Articles Training & Consulting

On the
blog
RSS feed November 4: Other Concurrency Sessions at PDC
November 3
: PDC'09: Tutorial & Panel
October 26: Hoare on Testing
October 23
: Deprecating export Considered for ISO C++0x

This is the original GotW problem and solution substantially as posted to Usenet. See the book Exceptional C++ (Addison-Wesley, 2000) for the most current solutions to GotW issues #1-30. The solutions in the book have been revised and expanded since their initial appearance in GotW. The book versions also incorporate corrections, new material, and conformance to the final ANSI/ISO C++ standard.

Casts
Difficulty: 6 / 10

How well do you know C++'s casts? Using them well can greatly improve the reliability of your code.

Problem

The new-style casts in (Draft) Standard C++ offer more power and safety than the old-style C casts. How well do you know them? The rest of this problem uses the following classes and global variables:

    class  A             { /*...*/ };
    class  B : virtual A { /*...*/ };
    struct C : A         { /*...*/ };
    struct D : B, C      { /*...*/ };

    A a1; B b1; C c1; D d1;
    const A a2;
    const A& ra1 = a1;
    const A& ra2 = a2;
    char c;

1. Which of the following new-style casts are NOT equivalent to a C cast?

const_cast

dynamic_cast

reinterpret_cast

static_cast

2. For each of the following C casts, write the equivalent new-style cast. Which are incorrect if not written as a new-style cast?

    void f() {
      A* pa; B* pb; C* pc;

      pa = (A*)&ra1;
      pa = (A*)&a2;
      pb = (B*)&c1;
      pc = (C*)&d1;
    }

3. Critique each of the following C++ casts for style and correctness.

    void g() {
      unsigned char* puc = static_cast<unsigned char*>(&c);
      signed char* psc = static_cast<signed char*>(&c);

      void* pv = static_cast<void*>(&b1);
      B* pb1 = static_cast<B*>(pv);

      B* pb2 = static_cast<B*>(&b1);

      A* pa1 = const_cast<A*>(&ra1);
      A* pa2 = const_cast<A*>(&ra2);

      B* pb3 = dynamic_cast<B*>(&c1);

      A* pa3 = dynamic_cast<A*>(&b1);

      B* pb4 = static_cast<B*>(&d1);

      D* pd = static_cast<D*>(pb4);

      pa1 = dynamic_cast<A*>(pb2);
      pa1 = dynamic_cast<A*>(pb4);

      C* pc1 = dynamic_cast<C*>(pb4);
      C& rc1 = dynamic_cast<C&>(*pb2);
  }

Solution

The new-style casts in (Draft) Standard C++ offer more power and safety than the old-style C casts. How well do you know them? The rest of this problem uses the following classes and global variables:

    class  A             { /*...*/ };
    class  B : virtual A { /*...*/ };
    struct C : A         { /*...*/ };
    struct D : B, C      { /*...*/ };

    A a1; B b1; C c1; D d1;
    const A a2;
    const A& ra1 = a1;
    const A& ra2 = a2;
    char c;

1. Which of the following new-style casts are NOT equivalent to a C cast?

Only dynamic_cast is not equivalent to some C cast. All other new-style casts have old-style equivalents.

2. For each of the following C casts, write the equivalent new-style cast. Which are incorrect if not written as a new-style cast?

    void f() {
      A* pa; B* pb; C* pc;

      pa = (A*)&ra1;

Use const_cast: pa = const_cast<A*>(&ra1);

      pa = (A*)&a2;

This cannot be expressed as a new-style cast. The closest candidate is const_cast, but since a2 is a const object the results are undefined.

      pb = (B*)&c1;

Use reinterpret_cast: pb = reinterpret_cast<B*>(&c1);

      pc = (C*)&d1;
    }

The above cast is wrong in C. In C++, no cast is required: pc = &d1;

3. Critique each of the following C++ casts for style and correctness.

First, a general note: We don't know whether any of these classes have virtual functions, and all of the following dynamic_casts are errors if the classes involved do not have virtual functions. For the rest of this discussion, we will assume that the classes do all have virtual functions, making the dynamic_casts legal.

    void g() {
      unsigned char* puc = static_cast<unsigned char*>(&c);
      signed char* psc = static_cast<signed char*>(&c);

Error: we must use reinterpret_cast for both. This might surprise you at first, but the reason is that char, signed char, and unsigned char are three distinct types. Even though there are implicit conversions between them, they are unrelated, and so pointers to them are unrelated.

      void* pv = static_cast<void*>(&b1);
      B* pb1 = static_cast<B*>(pv);

These are both fine, but the first is unnecessary since there is already an implicit conversion from an object pointer to a void*.

      B* pb2 = static_cast<B*>(&b1);

This is fine, but unnecessary since the argument is already a B*.

      A* pa1 = const_cast<A*>(&ra1);

This is legal, but casting away const is usually indicative of poor style. Most of the cases where you legitimately would want to remove the const-ness of a pointer or reference are related to class members and covered by the 'mutable' keyword. See GotW #6 for more discussion about const-correctness.

      A* pa2 = const_cast<A*>(&ra2);

Error: this will produce undefined behaviour if the pointer is used to write on the object, since a2 really is a const object. To see why, consider that a compiler is allowed to see that a2 is created as a const object and use that information to store it in read-only memory as an optimization. Casting away const on such an object is obviously dangerous.

Note: I showed no examples of using const_cast to convert a non-const pointer to a const pointer. The reason is that it's redundant; it is already legal to assign a non-const pointer to a const pointer. We only need const_cast to do the reverse.

      B* pb3 = dynamic_cast<B*>(&c1);

Error (if you try to use pb3): since c1 IS-NOT-A B (because C is not publicly derived from B, in fact it is not derived from B at all), this will set pb3 to null. The only legal cast would be a reinterpret_cast, and using that is almost always evil.

      A* pa3 = dynamic_cast<A*>(&b1);

Error: since b1 IS-NOT-A A (because B is not publicly derived from A, but its derivation is private), this is illegal.

      B* pb4 = static_cast<B*>(&d1);

This is fine, but not necessary since derived-to-base pointer conversions can be done implicitly.

      D* pd = static_cast<D*>(pb4);

This is fine, which may surprise you if you expected this to require a dynamic_cast. The reason is that downcasts can be static when the target is known, but beware: you are telling the compiler that you know for a fact that what is being pointed to really is of that type. If you are wrong, then the cast cannot inform you of the problem (as could dynamic_cast, which would return a null pointer if the cast failed) and at best you will get spurious runtime errors and/or program crashes.

      pa1 = dynamic_cast<A*>(pb2);
      pa1 = dynamic_cast<A*>(pb4);

These two look very similar. Both attempt to use dynamic_cast to convert a B* into an A*. However, the first is an error while the second is not.

Here's the reason: as noted above, you cannot use dynamic_cast to cast a pointer to what really is a B object (and here pb2 points to the object b1) into an A object, since B inherits privately, not publicly, from A. However, the second cast succeeds because pb4 points to the object d1, and D does have A as an indirect public base class (through C), and dynamic_cast is able to cast across the inheritance hierarchy using the path B* -> D* -> C* -> A*.

      C* pc1 = dynamic_cast<C*>(pb4);

This too is fine, for the same reason as the last: dynamic_cast can navigate the inheritance hierarchy and perform cross-casts, and so this is legal and will succeed.

      C& rc1 = dynamic_cast<C&>(*pb2);
  }

Finally, this isn't fine... since *pb2 isn't really a C, dynamic_cast will throw a bad_cast exception to signal failure. Why? Well, dynamic_cast can and does return null if a pointer cast fails, but since there's no such thing as a null reference it can't return a null reference if a reference cast fails. There's no way to signal such a failure to the client code besides throwing an exception, so that's what the standard bad_cast exception class is for.

Copyright 2009 Herb Sutter