RTTI,即运行时类型识别 ,由两个运算符实现:
typeid运算符,返回表达式的类型
dynamic_cast运算符,将基类指针或引用安全地转换成派生类的指针或引用
本篇主要阐述typeid运算符
typeid运算符 typeid表达式的形式是typeid(e),其中e可以是任意表达式或类型的名字,操作的结果是一个常量对象的引用,该对象的类型是标准库类型type_info或其公有派生类。
typeid运算符的一些特点:
忽略顶层const
如果表达式是引用类型,返回其所引用对象的类型,而不是type reference
作用于函数或数组时不会返回指针类型
当运算对象不属于类类型或者是一个不包含任何虚函数的类时,typeid运算符指示的是运算符对象的静态类型。例如,指向父类的指针和引用父类的引用,均返回其声明类型
当运算对象定义了至少一个虚函数,其类型直到运行时才会求得
typeid运算符使用示例程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int main () { const int ci = 0 ; int i = 0 ; cout << "type of ci: " << typeid (ci).name () << endl; cout << "type of i: " << typeid (i).name () << endl; assert (typeid (ci) == typeid (i)); double d = 0.9 ; double &rd = d; cout << "type of d: " << typeid (d).name () << endl; cout << "type of rd: " << typeid (rd).name () << endl; assert (typeid (d) == typeid (rd)); int a[10 ]; cout << "type of array int[10]: " << typeid (a).name () << endl; cout << "type of fucntion main: " << typeid (main).name () << endl; cout << "type of pointer to int: " << typeid (int *).name () << endl; return 0 ; }
输出结果为:
1 2 3 4 5 6 7 type of ci: i type of i: i type of d: d type of rd: d type of array int[10]: A10_i type of fucntion main: FivE type of pointer to int: Pi
当typeid作用于类类型时,基类和派生类是否具备虚函数会对结果有影响,我们使用如下两个类:base和derived验证。
1 2 3 4 5 6 7 8 9 class base { public : ~base () {} }; class derived : public base{ };
base中的虚析构函数被注释掉了,执行如下main函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int main () { base *p1 = new base; base *p2 = new derived; derived *p3 = new derived; cout << "type of *p1: " << typeid (*p1).name () << endl; cout << "type of *p2: " << typeid (*p2).name () << endl; cout << "type of *p3: " << typeid (*p3).name () << endl; base b1; derived d1; base &rb1 = b1; base &rb2 = d1; derived &rb3 = d1; cout << "type of rb1: " << typeid (rb1).name () << endl; cout << "type of rb2: " << typeid (rb2).name () << endl; cout << "type of rb3: " << typeid (rb3).name () << endl; return 0 ; }
输出结果如下(忽略了构造函数和析构函数的输出)
1 2 3 4 5 6 type of *p1: 4base type of *p2: 4base type of *p3: 7derived type of rb1: 4base type of rb2: 4base type of rb3: 7derived
取消对base虚析构函数的注释,再次运行:
1 2 3 4 5 6 type of *p1: 4base type of *p2: 7derived type of *p3: 7derived type of rb1: 4base type of rb2: 7derived type of rb3: 7derived
Note :typeid作用于指针上只会输出静态类型信息,不能获取其指向对象是什么类型。比如下面的测试代码,其中base和derived中包含有虚函数:
1 2 3 4 5 6 base *p1 = new base; base *p2 = new derived; derived *p3 = new derived; cout << "type of p1: " << typeid (p1).name () << '\t' << "type of *p1: " << typeid (*p1).name () << endl; cout << "type of p2: " << typeid (p2).name () << '\t' << "type of *p2: " << typeid (*p2).name () << endl; cout << "type of p3: " << typeid (p3).name () << '\t' << "type of *p3: " << typeid (*p3).name () << endl;
输出结果为:
1 2 3 type of p1: P4base type of *p1: 4basetype of p2: P4base type of *p2: 7derivedtype of p3: P7derived type of *p3: 7derived
typeid是否需要运行时检查决定了表达式是否会被求值。只有当类型含有虚函数时,编译器才会对表达式求值,反之,如果类型不含有虚函数,typeid返回表达式的静态类型。如果表达式的动态类型可能与静态类型不同,必须在运行时对表达式求值以确定返回的类型。因此,若指针指向的类型不含有虚函数,则其不必非得是有效指针。
使用RTTI 当设计类的时候,可能类中需要定义某个针对该类类型的对象的操作,则完成该操作的方法的参数一般是该类的类型。例如,为base类实现相等运算符。
1 2 3 4 5 6 7 8 9 class base { public : base () { cout << " \n base constructor \n" ; } bool operator ==(const base &); };
当设计一个derived类继承自base时,我们可能也需要为derived定义相等运算符。但虚函数的参数必须是相符的,因此derived类的定义看起来可能是这样的:
1 2 3 4 5 6 7 8 9 class derived : public base{ public : derived () { cout << " \n derived constructor \n" ; } bool operator ==(const base &); };
此时在derived中的相等运算符中,存在两个问题:
即使我们可以通过在base类中添加一个方法返回该私有成员,但第二个问题却难以解决。一个方法是在derived类的相等运算符方法中强制转换类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class base { public : base () = default ; base (int _a) : a (_a) { } virtual bool operator ==(const base &rhs) const { return a == rhs.a; } virtual bool operator !=(const base &rhs) const { return a != rhs.a; } int get_a () const { return a; } private : int a{}; }; class derived : public base{ public : derived () = default ; derived (int _a, int _b) : base (_a), b (_b) { } bool operator ==(const base &rhs) const { return base::operator ==(rhs) && b == (static_cast <const derived &>(rhs)).b; } bool operator !=(const base &rhs) const { return !(*this == rhs); } private : int b{}; };
倘若我们不小心比较了一个base类对象和derived类对象,可能最终结果是符合我们的预期,但是该过程是未定义的。要想实现真正有效的相等比较操作,首先我们需要认清楚一个事实:如果参与对比的两个对象类型不同,则比较结果为false。因此我们可以通过RTTI解决该问题:定义相等运算符的形参是基类的引用,使用typeid检查两个运算对象是否是同一类型,类型一致才继续对比成员,并通过dynamic_cast转换类型。在派生类中,我们也可以通过明确调用基类的方法对比基类部分的成员是否相等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 class base { friend bool operator ==(const base &lhs, const base &rhs) { return typeid (lhs) == typeid (rhs) && lhs.equal (rhs); } friend bool operator !=(const base &lhs, const base &rhs) { return !(lhs == rhs); } public : base () = default ; base (int _a) : a (_a) { } virtual bool equal (const base &rhs) const { return a == rhs.a; } private : int a{}; }; class derived : public base{ public : derived () = default ; derived (int _a, int _b) : base (_a), b (_b) { } bool equal (const base &rhs) const { auto r = dynamic_cast <const derived &>(rhs); return base::equal (rhs) && b == r.b; } private : int b{}; }; int main () { derived d1 (1 , 2 ) ; base b1 (1 ) ; assert (d1 != b1); base b2 (1 ) ; base b3 (2 ) ; assert (b2 != b3); base b4 (13 ) ; base b5 (13 ) ; assert (b4 == b5); derived d2 (11 , 12 ) ; derived d3 (11 , 10 ) ; assert (d2 != d3); derived d4 (11 , 12 ) ; derived d5 (11 , 12 ) ; assert (d4 == d5); cout << "Pass!\n" ; return 0 ; }
运行结果为:
type_info类 type_info 类的精确定义随着编译器的不同而略有差异,但c++标准规定必须定义在typeinfo头文件中,且至少提供如下4种操作:
操作
含义
t1 == t2
是否type_info对象t1和t2表示同一种类型
t1 != t2
是否type_info对象t1和t2表示不同类型
t.name()
返回一个c风格的字符串,表示类型名字的可打印形式
t1.before(t2)
返回一个bool值,表示t1是否位于t2之前
需要注意的是,type_info对象只能由typeid运算符创建 。如下程序测试c++ primer练习19.10:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class A { public : virtual ~A () {} }; class B : public A{ public : virtual ~B () {} }; class C : public B{ public : virtual ~C () {} }; int main () { A *pa = new C; cout << typeid (pa).name () << endl; C cobj; A &ra = cobj; cout << typeid (&ra).name () << endl; B *px = new B; A &rra = *px; cout << typeid (rra).name () << endl; delete px; return 0 ; }
运行结果如下: