Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
One way to determine the runtime type of an object through a
pointer or reference is to employ a runtime cast, which verifies that the attempted conversion is valid. This is useful when you need to cast a base-class
pointer to a derived type. Since inheritance hierarchies are typically depicted
with base classes above derived classes, such a cast is called a downcast.
Consider the following class hierarchy:
In the code that follows, the Investment class has an
extra operation that the other classes do not, so it is important to be able to
know at runtime whether a Security pointer refers to a Investment
object or not. To implement checked runtime casts, each class keeps an integral
identifier to distinguish it from other classes in the hierarchy.
//: C08:CheckedCast.cpp
// Checks casts at runtime.
#include <iostream>
#include <vector>
#include "../purge.h"
using namespace std;
class Security {
protected:
enum { BASEID = 0 };
public:
virtual ~Security() {}
virtual bool isA(int id) { return (id == BASEID); }
};
class Stock : public Security {
typedef Security Super;
protected:
enum { OFFSET = 1, TYPEID = BASEID + OFFSET };
public:
bool isA(int id) {
return id == TYPEID || Super::isA(id);
}
static Stock* dynacast(Security* s) {
return (s->isA(TYPEID)) ?
static_cast<Stock*>(s) : 0;
}
};
class Bond : public Security {
typedef Security Super;
protected:
enum { OFFSET = 2, TYPEID = BASEID + OFFSET };
public:
bool isA(int id) {
return id == TYPEID || Super::isA(id);
}
static Bond* dynacast(Security* s) {
return (s->isA(TYPEID)) ?
static_cast<Bond*>(s) : 0;
}
};
class Investment : public Security {
typedef Security Super;
protected:
enum { OFFSET = 3, TYPEID = BASEID + OFFSET };
public:
bool isA(int id) {
return id == TYPEID || Super::isA(id);
}
static Investment* dynacast(Security* s) {
return (s->isA(TYPEID)) ?
static_cast<Investment*>(s) : 0;
}
void special() {
cout << "special Investment
function" << endl;
}
};
class Metal : public Investment {
typedef Investment Super;
protected:
enum { OFFSET = 4, TYPEID = BASEID + OFFSET };
public:
bool isA(int id) {
return id == TYPEID || Super::isA(id);
}
static Metal* dynacast(Security* s) {
return (s->isA(TYPEID)) ?
static_cast<Metal*>(s) : 0;
}
};
int main() {
vector<Security*> portfolio;
portfolio.push_back(new Metal);
portfolio.push_back(new Investment);
portfolio.push_back(new Bond);
portfolio.push_back(new Stock);
for(vector<Security*>::iterator it =
portfolio.begin();
it != portfolio.end(); ++it) {
Investment* cm = Investment::dynacast(*it);
if(cm)
cm->special();
else
cout << "not an Investment"
<< endl;
}
cout << "cast from intermediate
pointer:" << endl;
Security* sp = new Metal;
Investment* cp = Investment::dynacast(sp);
if(cp) cout << " it's an Investment"
<< endl;
Metal* mp = Metal::dynacast(sp);
if(mp) cout << "
it's a Metal too!" << endl;
purge(portfolio);
} ///:~
The polymorphic isA( ) function checks to see if
its argument is compatible with its type argument (id), which means that
either id matches the object s typeID exactly or it matches one
of the object s ancestors (hence the call to Super::isA( ) in that
case). The dynacast( ) function, which is static in each class,
calls isA( ) for its pointer argument to check if the cast is
valid. If isA( ) returns true, the cast is valid, and a
suitably cast pointer is returned. Otherwise, the null pointer is returned,
which tells the caller that the cast is not valid, meaning that the original
pointer is not pointing to an object compatible with (convertible to) the
desired type. All this machinery is necessary to be able to check intermediate
casts, such as from a Security pointer that refers to a Metal
object to a Investment pointer in the previous example program.
For most programs downcasting is unnecessary, and is actually
discouraged, since everyday polymorphism solves most problems in object-oriented
application programs. However, the ability to check a cast to a more derived
type is important for utility programs such as debuggers, class browsers, and
databases. C++ provides such a checked cast with the dynamic_cast operator. The following program is a rewrite of the previous example using dynamic_cast:
//: C08:Security.h
#ifndef SECURITY_H
#define SECURITY_H
#include <iostream>
class Security {
public:
virtual ~Security() {}
};
class Stock : public Security {};
class Bond : public Security {};
class Investment : public Security {
public:
void special() {
std::cout << "special Investment
function <<std::endl;
}
};
class Metal : public Investment {};
#endif // SECURITY_H ///:~
//: C08:CheckedCast2.cpp
// Uses RTTI s dynamic_cast.
#include <vector>
#include "../purge.h"
#include "Security.h"
using namespace std;
int main() {
vector<Security*> portfolio;
portfolio.push_back(new Metal);
portfolio.push_back(new Investment);
portfolio.push_back(new Bond);
portfolio.push_back(new Stock);
for(vector<Security*>::iterator it =
portfolio.begin();
it != portfolio.end(); ++it) {
Investment* cm =
dynamic_cast<Investment*>(*it);
if(cm)
cm->special();
else
cout << "not a Investment"
<< endl;
}
cout << "cast from intermediate pointer:
<< endl;
Security* sp = new Metal;
Investment* cp = dynamic_cast<Investment*>(sp);
if(cp) cout << " it's an Investment
<< endl;
Metal* mp = dynamic_cast<Metal*>(sp);
if(mp) cout << " it's a Metal too!
<< endl;
purge(portfolio);
} ///:~
This example is much shorter, since most of the code in the
original example was just the overhead for checking the casts. The target type
of a dynamic_cast is placed in angle brackets, like the other new-style
C++ casts (static_cast, and so on), and the object to cast appears as
the operand. dynamic_cast requires that the types you use it with be polymorphic if you want safe downcasts. This
in turn requires that the class must have at least one virtual function.
Fortunately, the Security base class has a virtual destructor, so we
didn t have to invent an extra function to get the job done. Because dynamic_cast
does its work at runtime, using the virtual table, it tends to be more
expensive than the other new-style casts.
You can also use dynamic_cast with references instead
of pointers, but since there is no such thing as a null reference, you need
another way to know if the cast fails. That other way is to catch a bad_cast exception, as follows:
//: C08:CatchBadCast.cpp
#include <typeinfo>
#include "Security.h"
using namespace std;
int main() {
Metal m;
Security& s = m;
try {
Investment& c =
dynamic_cast<Investment&>(s);
cout << "It's an Investment"
<< endl;
} catch(bad_cast&) {
cout << "s is not an Investment
type" << endl;
}
try {
Bond& b = dynamic_cast<Bond&>(s);
cout << "It's a Bond" <<
endl;
} catch(bad_cast&) {
cout << "It's not a Bond type"
<< endl;
}
} ///:~
The bad_cast class is defined in the <typeinfo> header, and, like most of the standard library, is declared in the std
namespace.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |