|
|
|
|
Downcasting
As you might guess, since there’s
such a thing as upcasting – moving up an
inheritance hierarchy – there should also be
downcasting to move down
a hierarchy. But upcasting is easy since as you move up an inheritance hierarchy
the classes always converge to more general classes. That is, when you upcast
you are always clearly derived from an ancestor class (typically only one,
except in the case of multiple inheritance) but when you downcast there are
usually several possibilities that you could cast to. More specifically, a
Circle is a type of Shape (that’s the upcast), but if you
try to downcast a Shape it could be a Circle, Square,
Triangle, etc. So the dilemma is figuring out a way to safely downcast.
(But an even more important issue is asking yourself why you’re
downcasting in the first place instead of just using polymorphism to
automatically figure out the correct type. The avoidance of downcasting is
covered in Volume 2 of this book.)
C++ provides a special
explicit cast (introduced
in Chapter 3) called
dynamic_cast that is a
type-safe downcast operation. When you use
dynamic_cast to try to cast down to a particular type, the return value
will be a pointer to the desired type only if the cast is proper and successful,
otherwise it will return zero to indicate that this was not the correct type.
Here’s a minimal example:
//: C15:DynamicCast.cpp
#include <iostream>
using namespace std;
class Pet { public: virtual ~Pet(){}};
class Dog : public Pet {};
class Cat : public Pet {};
int main() {
Pet* b = new Cat; // Upcast
// Try to cast it to Dog*:
Dog* d1 = dynamic_cast<Dog*>(b);
// Try to cast it to Cat*:
Cat* d2 = dynamic_cast<Cat*>(b);
cout << "d1 = " << (long)d1 << endl;
cout << "d2 = " << (long)d2 << endl;
} ///:~
When you use dynamic_cast, you
must be working with a true polymorphic hierarchy – one with
virtual functions – because dynamic_cast
uses information stored in the VTABLE to determine the actual type. Here, the
base class contains a virtual destructor and that suffices. In
main( ), a Cat pointer is upcast to a Pet, and then a
downcast is attempted to both a Dog pointer and a Cat pointer.
Both pointers are printed, and you’ll see when you run the program that
the incorrect downcast produces a zero result. Of course, whenever you downcast
you are responsible for checking to make sure that the result of the cast is
nonzero. Also, you should not assume that the pointer will be exactly the same,
because sometimes pointer adjustments take place during upcasting and
downcasting (in particular, with multiple inheritance).
A dynamic_cast requires a little
bit of extra overhead to run; not much, but if you’re doing a lot of
dynamic_casting (in which case you should be seriously questioning your
program design) this may become a performance issue. In some cases you may know
something special during downcasting that allows you to say for sure what type
you’re dealing with, in which case the extra overhead of the
dynamic_cast becomes unnecessary, and you can use a
static_cast instead.
Here’s how it might work:
//: C15:StaticHierarchyNavigation.cpp
// Navigating class hierarchies with static_cast
#include <iostream>
#include <typeinfo>
using namespace std;
class Shape { public: virtual ~Shape() {}; };
class Circle : public Shape {};
class Square : public Shape {};
class Other {};
int main() {
Circle c;
Shape* s = &c; // Upcast: normal and OK
// More explicit but unnecessary:
s = static_cast<Shape*>(&c);
// (Since upcasting is such a safe and common
// operation, the cast becomes cluttering)
Circle* cp = 0;
Square* sp = 0;
// Static Navigation of class hierarchies
// requires extra type information:
if(typeid(s) == typeid(cp)) // C++ RTTI
cp = static_cast<Circle*>(s);
if(typeid(s) == typeid(sp))
sp = static_cast<Square*>(s);
if(cp != 0)
cout << "It's a circle!" << endl;
if(sp != 0)
cout << "It's a square!" << endl;
// Static navigation is ONLY an efficiency hack;
// dynamic_cast is always safer. However:
// Other* op = static_cast<Other*>(s);
// Conveniently gives an error message, while
Other* op2 = (Other*)s;
// does not
} ///:~
In this program, a new feature is used
that is not fully described until Volume 2 of this book, where a chapter is
given to the topic: C++’s
run-time
type information (RTTI) mechanism. RTTI allows you to discover type
information that has been lost by upcasting. The dynamic_cast is actually
one form of RTTI. Here, the
typeid keyword (declared
in the header file
<typeinfo>)
is used to detect the types of the pointers. You can see that the type of
the upcast Shape pointer is successively compared to a Circle
pointer and a Square pointer to see if there’s a match.
There’s more to RTTI than typeid, and you can also imagine that it
would be fairly easy to implement your own type information system using a
virtual function.
A Circle object is created and the
address is upcast to a Shape pointer; the second version of the
expression shows how you can use static_cast to be more explicit about
the upcast. However, since an upcast is always safe and it’s a common
thing to do, I consider an
explicit
cast for upcasting to be cluttering and unnecessary.
RTTI is used to determine the type, and
then static_cast is used to perform the downcast. But notice that in this
design the process is effectively the same as using dynamic_cast, and the
client programmer must do some testing to discover the cast that was actually
successful. You’ll typically want a situation that’s more
deterministic than in the example above before using static_cast rather
than dynamic_cast (and, again, you want to carefully examine your design
before using dynamic_cast).
If a class hierarchy has no
virtual functions (which is a questionable design) or if you have other
information that allows you to safely downcast, it’s a tiny bit faster to
do the downcast statically than
with dynamic_cast. 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.
|
|
|