向中间层造型
正如你在前面使用Security类层次里所看到的,dynamic_cast能在一个有多个层的继承层次里探测到原来的类型和中间的类型。这儿是另一个例子:
//: C08:IntermediateCast.cpp
#include <cassert>
#include <typeinfo>
using namespace std;
class B1 {
public:
virtual ~B1() {}
};
class B2 {
public:
virtual ~B2() {}
};
class MI : public B1, public B2 {};
class Mi2 : public MI {};
int main() {
B2* b2 = new Mi2;
Mi2* mi2 = dynamic_cast<Mi2*>(b2);
MI* mi = dynamic_cast<MI*>(b2);
B1* b1 = dynamic_cast<B1*>(b2);
assert(typeid(b2) != typeid(Mi2*));
assert(typeid(b2) == typeid(B2*));
delete b2;
} ///:~
这个例子有多重继承的额外复杂性(你在本章的后面第9章学习更多关于多重继承的内容)。如果你创建了一个Mi2而且把他向上转换到根(在这种情况下,两个可能根中的一个被选到),dynamic_cast回到继承层MI或者Mi2是成功的。
你甚至能从一个根造型到另一个:
B1* b1 = dynamic_cast<B1*>(b2);
这是成功的因为B2事实上指向一个Mi2对象,它包含了一个类型为B1.的子对象。
向中间层造型在dynamic_cast和typeid之间产生了一个有趣的差异。Typeid操作符经常产生指向一个静态type_info对象的引用,该type_info对象描述了对象的动态型别。因此,他不会给你中间层的信息。在下面的表达式里(它为true), typeid不会将b2看作指向衍生类型的指针,象dynamic_cast一样:
typeid(b2) != typeid(Mi2*)
b2的类型就是该指针的实际类型:typeid(b2) == typeid(B2*)
void指针
RTTI仅仅对完整类型起作用,意味着使用typeid的时候所有的类信息都是可能用到。特别的,它不对void指针起作用:
//: C08:VoidRTTI.cpp
// RTTI & void pointers.
//!#include <iostream>
#include <typeinfo>
using namespace std;
class Stimpy {
public:
virtual void happy() {}
virtual void joy() {}
virtual ~Stimpy() {}
};
int main() {
void* v = new Stimpy;
// Error:
//! Stimpy* s = dynamic_cast<Stimpy*>(v);
// Error:
//! cout << typeid(*v).name() << endl;
} ///:~
Void实际上意味着“没有类型信息”。
RTTI用于模版
类模版与RTTI工作正常,因为它所有的事情就是产生类。像平常一样,RTTI提供了一个方便的获得你所在的类的名字的方法。下面的例子打印出构造函数和析构函数调用的顺序。
//: C08:ConstructorOrder.cpp
// Order of constructor calls.
#include <iostream>
#include <typeinfo>
using namespace std;
template<int id> class Announce {
public:
Announce() {
cout << typeid(*this).name() << " constructor" << endl;
}
~Announce() {
cout << typeid(*this).name() << " destructor" << endl;
}
};
class X : public Announce<0> {
Announce<1> m1;
Announce<2> m2;
public:
X() { cout << "X::X()" << endl; }
~X() { cout << "X::~X()" << endl; }
};
int main() { X x; } ///:~
这个模版用一个常整型来区别一个类和另一个类,但是类型参数也能工作。在构造函数和析构函数内部,RTTI信息产生了打印的类名字。类X用继承和组合来产生一个类,该类的构造和析构函数调用顺序很有趣。输出是:
Announce<0> constructor
Announce<1> constructor
Announce<2> constructor
X::X()
X::~X()
Announce<2> destructor
Announce<1> destructor
Announce<0> destructor
当然,你也能得到不同的输出,依赖于你的编译器怎样表现它的name( )信息。