dynamic_cast

Boris Kolpackov boris at kolpackov.net
Thu Dec 18 12:10:01 CST 2003


Good day,

In my recent project (compiler) I got exposed to some obscure 
features of C++ dynamic_cast operator. After encountering a few 
surprises I decided to test dynamic_cast in static (pun intended) 
and here are some of my observations.


First of all, dynamic_cast has four distinguishable outcomes:

* run-time failure 

  Results in std::bad_cast exception in case of a reference and 
  0 in case of a pointer.


* run-time conversion

  Results in an accordingly adjusted at run-time reference or 
  pointer.


* compile-time failure 

  Diagnosed by a compiler as an error.


* compile-time conversion

  Results in an accordingly adjusted at compile-time reference or 
  pointer. Note that in this case no run-time checking is performed.


The latter two outcomes may be a surprise to you but here is the 
case (and the only case, to my knowledge) in which everything is
done at compile-time:

struct A
{
  virtual ~A () {}
};

struct B1 : A
{
};

struct B2 : A
{
};

struct C : B1, B2
{
};

void f ()
{
  C c;

  dynamic_cast<A&> (c);   // compile-time error: ambiguous

  B1& b1 = c;

  dynamic_cast<A&> (b1); // compile-time conversion
};

This case is described in clause 5.2.7 paragraph 5 of The C++ Standard
and boils down to conversion to accessible unambiguous base. Note also 
that, even though C doesn't have unambiguous base of type A, the 
conversion still succeeds.

Another interesting feature is dynamic_cast to void* (described in
clause 5.2.7 paragraph 7). One might expect that it would be 
equivalent to implicit conversion to void*. The following example 
examines the difference:

struct A
{
  virtual ~A () {}
  
  long a;
};

struct B
{
  virtual ~B () {}
  
  long b;
};

struct C : A, B
{
};

int main ()
{
  C c;
  
  C* cp = &c;
  B* bp = cp;

  void* sv = bp;
  void* dv = dynamic_cast<void*> (bp);

  cerr << "cp = " << cp << endl;
  cerr << "bp = " << bp << endl;
  cerr << "sv = " << sv << endl;
  cerr << "dv = " << dv << endl;
};

On my box the example prints

cp = 0xbffffa50
bp = 0xbffffa58
sv = 0xbffffa58
dv = 0xbffffa50

which shows that dynamic_cast<void*> returns pointer to the most
derived object pointed to by the argument. Note that the argument
should be a pointer/reference to a polymorphic type.


Protection violation and ambiguity are the common causes for 
conversion failure. As surprising it may sound, in dynamic_cast
they lead to run-time failures. Consider the following example
(covers protection violation - ambiguity is analogous):

struct A
{
  virtual ~A () {}
};

struct B
{
  virtual ~B () {}
};

struct C : A, protected B
{
};

void g ()
{
  C c;
  
  A& a = c;
  B& b = (B&)(c);       // subvert protection
  
  dynamic_cast<B&> (a); // run-time failure
  dynamic_cast<A&> (b); // run-time failure
}

struct D : A, B
{
};

struct E : protected D
{
  void f ()
  {
    A& a = *this;
    dynamic_cast<B&> (a);
  }
};

void h ()
{
  E e;
  A& a = (A&)(e);       // subvert protection

  dynamic_cast<B&> (a); // run-time failure
    
  e.f ();               // run-time failure even though executed
                        // in member function

  dynamic_cast<D&> (a); // ok
};


The difference between the last two conversions is somewhat subtle
and specified in clause 5.2.7 paragraph 5. To better understand all
these cases you can view inheritance as a graph and dynamic_cast as
a traverser that can navigate through edges in any direction but only
if they are public and unambiguous. Plus one small condition: in order
for dynamic_cast to jump to a sibling (from A to B in D-inheritance) it
has to be able to traverse to the most derived object (E in our case).

You may also be surprised that certain 'obvious' cases are not handled
at compile-time. Consider for instance this example:

struct A
{
  virtual ~A ()
  {
  }
};

struct B : protected virtual A
{
};

void k ()
{
  C c;	
  A& a = (A&)(c);
  
  dynamic_cast<B&> (a); // run-time failure
}

Since B has only protected base of type A then dynamic_cast<B&> (A&)
should always fail. Apparently this is not the case:

struct D : virtual A
{
};

struct E : D, B
{
};

void l ()
{
    E e;
    D& d = e;
    A& a = d;

    dynamic_cast<B&> (a); // ok
}

In this example compiler cannot take A->B path. Instead it takes
longer but legal path A->D->E->B.

This also shows that protection information is preserved in translated
programs and not discarded after static analysis.

Another relevant to dynamic_cast C++ feature is virtual base class. As
you may know, we cannot use static_cast to 'up-cast' from virtual base
to derived and dynamic_cast is the only option:

struct A
{
  virtual ~A () {}
};

struct B : virtual A
{
};

void m ()
{
  B b;
  A& a = b;

  static_cast<B&> (a);  // compile-time failure
  dynamic_cast<B&> (a); // ok
}

To understand why we can't use static_cast with virtual bases consider
this example:

struct C : virtual A
{
};

struct D : C, B
{
};


Here either C or B (or both) will have to 'give up' their instance of
A in order to share the common copy. As a result compiler has no way
to decide at compile-time whether a pointer to A is B's own copy of A
(as in the former case) or it is somebody else's copy of A that B is 
sharing (as may happen in the latter case).


And finally, to show how crafty dynamic_cast can be, one practical 
example:

struct A
{
  virtual ~A () {}
};

struct B : A
{
};

namespace N
{
  struct B
  {
    virtual ~B () {}
  };

  void f (A& n)
  {
    dynamic_cast<B&> (n);
  }
}

Do you see the problem? Do you think compiler will warn you? Of 
course, you may say, A and N::B are two unrelated types thus compiler
will never be able to convert A& to N::B&! Well, this is not quite 
correct. The following definition would establish the relationship
between A and N::B which is just good enough for dynamic_cast:

struct C : A, N::B
{
};

And since compiler cannot foresee that there is no such relationship
it has no other choice than to perform run-time check.


hth,
-boris
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 652 bytes
Desc: Digital signature
Url : http://www.kolpackov.net/pipermail/notes/attachments/20031218/9d558524/attachment.bin


More information about the notes mailing list