To perform a downcast is to cast a pointer to a class into a pointer
to a subclass. The three standard ways of doing it are:
- Using
dynamic_cast
- Using a virtual function
- Using a type tags and
static_cast
In the remaining section, we will use
source class to denote
the class being cast from and
target class to denote the
class that we are casting to.
Using dynamic_cast
Of the three methods, using a
dynamic_cast is the
simplest one. Unfortunately, on the systems measured, it is also the
slowest way of performing a downcast. To use dynamic cast, the
compiler has to generate code for RTTI and the class has to contain at
least one virtual function. If you are going to inherit from a class,
you should make the destructor virtual, so that is automatically
solved. The code to perform the downcast is:
if (Derived* d = dynamic_cast<Derived*>(b)) {
...
}
- Advantages:
- The class does not have to be designed in any special way.
- Disadvantages:
- Requires RTTI to be compiled in
Using virtual function
To use a virtual function to perform the downcast, we require each the
base class to define a virtual function for the target class of the
downcast, and then the target class overrides this function and
returns itself. The following code illustrates how to do this:
class Derived; // Forward declaration
class Base {
public:
virtual Derived* IsDerived() { return 0; }
};
class Derived : public Base {
public:
virtual Derived* IsDerived() { return this; }
};
To perform a downcast using this method, we can then use the
following code:
Base* b = ...;
.
.
.
if (Derived* d = b->IsDerived()) {
...
}
- Advantages:
- Does not require RTTI
- Easy to get right
- Disadvantages:
- Requires defining virtual functions for every subclass that
can be the target of a downcast.
The fastest of the methods for performing a downcast is by handling
the type tags yourself by using an enum and a
static_cast. It is also the method that is the least
flexible and which requires the most from the programmer. The
following code illustrates how to implement this method:
class Base {
public:
enum Type { BASE, DERIVED };
Type type() const { return mType; }
Base() : mType(BASE) { }
enum { TAG = BASE }; // For the down_cast function below
protected:
// Protected since it's only intended for subclasses
Base(Type t) : mType(t) { }
private:
Type mType;
};
class Derived : public Base {
public:
Derived() : Base(Base::DERIVED) { }
enum { TAG = DERIVED }; // For the down_cast function below
};
The method can then be used in the following manner:
Base* b = ...;
.
.
.
Derived* d = b->type() == DERIVED ? static_cast<Derived*>(b) : 0;
To simplify the usage, we can introduce the following template
function to do the job:
template <class Target, class Source>
Target* down_cast(Source* s) {
return s->type() == Target::TAG ? static_cast<Target*>(s) : 0;
}
Then we can use it in almost the same way that you use the other
casts:
if (Derived* d = down_cast<Derived>(b)) {
...
}
It is possible to make it look exactly like the other casts, but that
requires some more work with templates. See
the afternote below for the
source code for that.
- Advantages:
-
- Disadvantages:
- Requires extending an enum with a new type tag for each
class that can be the target of a downcast.
- Hard to maintain for large number of subclasses
Measurements
All measurements done are on an
Intel(R) Pentium(R) M
processor 1.60GHz using GCC with -O3 optimization level (mainly to get function inlining).
Downcasting (execution time)
|
Down-cast method |
Factor |
| Length |
dynamic_cast |
virtual call |
static_cast |
DC/VC |
VC/SC |
DC/SC |
| 2000 | 0.16 | 0.02 | 0.01 | 6.62 | 2.67 | 17.67 |
| 4000 | 0.32 | 0.05 | 0.02 | 6.53 | 2.72 | 17.78 |
| 6000 | 0.48 | 0.07 | 0.03 | 6.50 | 2.64 | 17.18 |
| 8000 | 0.63 | 0.10 | 0.04 | 6.48 | 2.58 | 16.71 |
| 10000 | 0.80 | 0.12 | 0.05 | 6.39 | 2.72 | 17.37 |
| 12000 | 0.97 | 0.15 | 0.06 | 6.58 | 2.58 | 16.96 |
| 14000 | 1.11 | 0.17 | 0.07 | 6.56 | 2.58 | 16.89 |
| 16000 | 1.28 | 0.20 | 0.08 | 6.48 | 2.61 | 16.88 |
| 18000 | 1.43 | 0.22 | 0.08 | 6.50 | 2.59 | 16.84 |
| 20000 | 1.61 | 0.25 | 0.10 | 6.51 | 2.57 | 16.74 |
Summary
Of the three methods to perform a down cast, using a type tag and a
static cast is by far the most efficient way: about 17 times faster
than using
dynamic_cast and almost 3 times as efficient as using a
virtual function. There is an added advantage that only non-virtual
functions are used to perform the cast, which potentially affects the
surrounding code since it allows the compiler to take advantage of the
fact that the function is inlined and the internals available to the
optimizer.
Afternote: implementation of
down_cast
In order to make the
down_cast above appear as a normal
cast, we have to use different code depending on whether we are
casting to a pointer or to a reference. Casting to a value is not
meaningful for this use, so we elect to only implement casting a
pointer and a reference downwards in a class hierarchy. To handle
that, we introduce a helper class
down_caster, and then
use a template function to let the compiler figure out the proper type
to use for the values.
template <class, class> struct down_caster;
template <class To, class From>
struct down_caster<To*, From*> {
static To* cast(From* from) {
return from->type() == To::TAG ? static_cast<To*>(from) : 0;
}
};
template <class To, class From>
struct down_caster<To&, From>
{
static To& cast(From& from) {
if (from.type() == To::TAG)
return static_cast<To&>(from);
else
throw std::bad_cast();
}
};
template <class To, class From>
struct down_caster<const To*, From*> {
static const To* cast(From* from) {
return from->type() == To::TAG ? static_cast<const To*>(from) : 0;
}
};
template <class To, class From>
struct down_caster<const To&, From>
{
static const To& cast(From& from) {
if (from.type() == To::TAG)
return static_cast<const To&>(from);
else
throw std::bad_cast();
}
};
Especially note the
From when casting to a reference
type. The reason for this can best be explained through an example. Consider this code:
Derived& foo(Base *pbase) {
return down_cast<Derived&>(*pbase);
}
The code is legal and will resolve the second template parameter of
down_cast to be
Base. If we used the
template parameter
From&, it would not match
Base and generate a compile error, even though the code
above is reasonable and shall compile (compare it with how
dynamic_cast would behave).