这篇文章是C++ RTTI的后续,前面我们介绍了typeid()操作符,这篇文章介绍RTTI的另一个概念,即dynamic_cast。
相比较typeid,dynamic_cast在实际项目中会被大量使用。
首先,一句话dynamic_cast是干什么用的,dynamic_cast是在两个类之间做类型转换的,即把一个指针类型转换成另一个类型;这个转换过程是在运行时刻转换的,所以叫dynamic_cast(与此相对的是static_cast)。dynamic_cast的使用必须满足下面两个条件:
- 如果两个类之间没有父子关系,那么source必须是多态的。
- 如果是从父类到子类的转换,那么父类(source)也必须多态的。
否则编译器就报错:
<file>:41: error: cannot dynamic_cast 'pc' (of type 'class <SRC> *') to type 'class <DST> *' (source type is not polymorphic)
我们重新解释一下上述描述:dynamic_cast的使用需要source是多态的,只有一种情况例外,那就是从子类到父类的转换。再换一个角度,其实dynamic_cast的使用必须要source是多态的,因为同学们想想,例外情况中子类到父类的转换是天然的行为,根本不需要dynamic_cast啊,像(Parent *)child。
我们看一段代码:
#include <stdio.h>
#include <string>
class A1 {};
class A2 : public A1 {};
void foo(A2 * pa2) {
A1 * va1 = dynamic_cast<A1 *>(pa2);
}
int main(int argc, char * argv[]) {
A2 * a2 = new A2();
foo(a2);
return 0;
}
编译器生成的汇编代码如下:
_Z3fooP2A2:
pushq %rbp
movq %rsp, %rbp
movq %rdi, -24(%rbp)
movq -24(%rbp), %rax
movq %rax, -8(%rbp)
leave
ret
可见编译器直接把pa2的赋给了va1,根本没有调用到dynamic_cast,因为编译器明确知道父类和子类的关系以及成员构成,又没有多态的问题,所以编译器就能够完成类型转换工作,其实此时dynamic_cast是被映射成了static_cast使用。
讨论完使用场合,下面我们看看转换的结果(假定source都是多态的):
- 如果source和dest没有父子关系,那么结果是NULL,即使两个类定义成一模一样。
- 从子类到父类,前面说过必然是成功的。
- 从父类到子类,这也是dynamic_cast最常用的情况,这依赖于父类指针是否真实的指向了对应的子类类型,标示这是不是一个真实的子类对象。
#include <stdio.h>
#include <string>
class A {
public:
virtual ~A() {}
};
class B1: public A {};
class B2: public A {};
void foo(A * pa) {
B1 * vb1 = dynamic_cast<B1 *>(pa);
printf("%s\n", vb1 == NULL ? "NULL" : "NOT NULL");
}
int main(int argc, char * argv[]) {
B1 * b1 = new B1();
B2 * b2 = new B2();
foo(b1);
foo(b2);
return 0;
}
运行结果如下:
NOT NULL
NULL
可见b1是能转换成功的,b2不能成功,因为虽然b1和b2都是A的实例,但是b2不是B1的实例。
前面我们介绍了dynamic_cast的使用场景,现在我们介绍dynamic_cast是如何工作的;代码说明问题:
#include <stdio.h>
#include <string>
class A {
public:
virtual ~A() {}
};
class B: public A {};
void foo(A * pa) {
B * vb = dynamic_cast<B *>(pa);
}
int main(int argc, char * argv[]) {
B * b = new B();
foo(b);
return 0;
}
类A是类B的父类,我们看生成的foo()函数代码:
_Z3fooP1A:
pushq %rbp
movq %rsp, %rbp
subq $32, %rsp
movq %rdi, -24(%rbp)
movq -24(%rbp), %rax
testq %rax, %rax
jne .L9
movl $0, %eax
jmp .L10
.L9:
movl $0, %ecx
movl $_ZTI1B, %edx
movl $_ZTI1A, %esi
movq %rax, %rdi
call __dynamic_cast
.L10:
movq %rax, -8(%rbp)
leave
ret
首先验证参数pa是否为NULL,如果是则直接返回NULL,如果不是则调用C++ lib库函数__dynamic_cast。
库函数__dynamic_cast需要四个参数:
extern "C" void*
__dynamic_cast (const void *v,
const abi::__class_type_info *src,
const abi::__class_type_info *dst,
std::ptrdiff_t src2dst_offset)
参数声明为:
- v: source对象地址,NOT NULL(前面源代码我们看的如果是NULL就不会进到这儿来了),并且由于source是多态的,那么source对象的第一个域是指向虚函数表的指针。
- src: source对象的类类型
- dat: destination对象的类类型
- 这个参数我也没弄清楚,但是当src是dst的基类时为-2,当src和dst不相干时为0。
关于这个函数的详细说明搜搜C++ ABI文档,比如: https://android.googlesource.com/platform/abi/cpp/+/6426040f1be4a844082c9769171ce7f5341a5528/src/dynamic_cast.cc
#define DYNAMIC_CAST_NO_HINT -1
#define DYNAMIC_CAST_NOT_PUBLIC_BASE -2
#define DYNAMIC_CAST_MULTIPLE_PUBLIC_NONVIRTUAL_BASE -3
/* v: source address to be adjusted; nonnull, and since the
* source object is polymorphic, *(void**)v is a virtual pointer.
* src: static type of the source object.
* dst: destination type (the "T" in "dynamic_cast<T>(v)").
* src2dst_offset: a static hint about the location of the
* source subobject with respect to the complete object;
* special negative values are:
* -1: no hint
* -2: src is not a public base of dst
* -3: src is a multiple public base type but never a
* virtual base type
* otherwise, the src type is a unique public nonvirtual
* base type of dst at offset src2dst_offset from the
* origin of dst.
*/
下面我们再看src(class A)和dst(class B)两个参数的值定义:
_ZTS1B:
.string "1B"
_ZTS1A:
.string "1A"
_ZTI1B:
.quad _ZTVN10__cxxabiv120__si_class_type_infoE+16
.quad _ZTS1B
.quad _ZTI1A
_ZTI1A:
.quad _ZTVN10__cxxabiv117__class_type_infoE+16
.quad _ZTS1A
参数src的类型时$_ZTI1A, 参数dst的类型时$_ZTI1B;从内容可以看出这两个类型的定义也是不一样的
- $_ZTI1A的类型是__class_type_info
- $_ZTI1B的类型是__si_class_type_info,是__class_type_info的子类;子类包含一个指向父类类类型的指针:
const __class_type_info* __base_type;
参阅 /usr/include/c++/4.4.4/cxxabi.h
所有这些类的类型信息都在应用程序启动的时候,在main函数入口之前注册到全局变量里,使得在用户程序能够访问到他们。
最后总结一下dynamic_cast的使用场景
- dynamic_cast用来实现指针类型的转化。
- dynamic_cast如果转化成功则返回指向目标类型的指针,如果转换不成功返回NULL。
- dynamic_cast要求src必须是多态的,因为dynamic_cast需要从类的虚函数表表中获得类类型信息。
- dynamic_cast最常见的用法是从一个抽象基类转换到具体的实现类。
当一个父类有多种子类时,如果目前有一个指向父类的指针,但是我们不知道指向父类的指针实际上指向的是哪一种子类,可以使用dynamic_cast<Child *>来判断,如果返回不是NULL,说明这是一个指向Child子类的指针,否则就不是。
网友评论