目录
前言
运行时类型识别。在有多态行为发生的继承体系中,通过RTTI,程序能够使用基类的指针或者引用来检查这些指针或者引用所指的实际对象的类型。RTTI的实现是由dynamic_cast和typeid运算符来实现的。基类存在虚函数,且子类继承并实现了拥有自己行为的虚函数,那么在基类指针或者引用指向子类对象的时候,就会发生动态绑定,也叫做晚绑定。此时基类指针或者引用的实际类型是子类对象的类型。
一、dynamic_cast
1.向上转换(Casting Up)
向上转型或者说从派生类转换到基类都是可以的,只要不存在二义性。它还可以被隐式的执行。
#include <iostream>
struct A {
virtual void f() {}
virtual ~A() {}
int ma;
};
struct B : A {
float mb;
int fb() { return 3; }
};
struct C : A {};
struct D : B, C {};
void f(A a) {}
void g(A& a) {}
void h(A* a) {}
int main()
{
B b;
f(b); //b转换为基类对象发生了切割,
g(b);
h(&b);
return 0;
}
在这三种情况下,对象b都被隐式转换成类型A的对象。注意,函数f不是多态的,因为多态类型必须以引用或(智能)指针的方式传递。只有当基类存在二义性的时候,向上转换才会失败。例如在这个例子中,我们无法将D 的对象转换到A,因为编译器不知道基类A是从B还是C来的。为了澄清这个二义性,我们需要明确指定中间的转换步骤:先将D对象明确转换为B或者C类型
D d;
f((B)d);
或者我们可以让A作为B和C的虚基类:
#include <iostream>
struct A {
virtual void f() {}
virtual ~A() {}
int ma;
};
struct B : virtual A {
float mb;
int fb() { return 3; }
};
struct C : virtual A {};
struct D : B, C {};
void f(A a) {}
void g(A& a) {}
void h(A* a) {}
int main()
{
D d;
f(d);
return 0;
}
此时,A的成员在D中只会出现一次。在多数情况下,这通常是多重继承的最佳解决方案。
2.向下转型(Casting Down)
向下转型是将指针或者引用转换成子类型的指针或者引用。如果实际引用的对象不是转换的子类型,那么就会导致未定义的行为。因此,只有在绝对必要的条件下才能极为谨慎的使用它。
在上面向上转型的例子中,我们将类型B的对象传递给了引用A& 或者指针A*,在函数g和h中,尽管被引用的对象b是B类型,但是还是不能访问B的成员变量mb和成员函数fb()。在确保函数参数a确实引用了B类型的对象后,可以将a分别向下转型为B& 或者 B*,然后就可以访问mb和fb()了。
在我们准备向程序中引入向下转型之前,必须考虑以下问题:
1、我们如何保证传递给函数的参数确实是派生类的对象?
2、如果不能向下转型我们应该怎么办?