美文网首页代码世界C++程序员
C++ RTTI的dynamic_cast函数

C++ RTTI的dynamic_cast函数

作者: CodingCode | 来源:发表于2017-06-01 22:41 被阅读297次

    这篇文章是C++ RTTI的后续,前面我们介绍了typeid()操作符,这篇文章介绍RTTI的另一个概念,即dynamic_cast。
    相比较typeid,dynamic_cast在实际项目中会被大量使用。


    首先,一句话dynamic_cast是干什么用的,dynamic_cast是在两个类之间做类型转换的,即把一个指针类型转换成另一个类型;这个转换过程是在运行时刻转换的,所以叫dynamic_cast(与此相对的是static_cast)。dynamic_cast的使用必须满足下面两个条件:

    1. 如果两个类之间没有父子关系,那么source必须是多态的。
    2. 如果是从父类到子类的转换,那么父类(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都是多态的):

    1. 如果source和dest没有父子关系,那么结果是NULL,即使两个类定义成一模一样。
    2. 从子类到父类,前面说过必然是成功的。
    3. 从父类到子类,这也是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)
    

    参数声明为:

    1. v: source对象地址,NOT NULL(前面源代码我们看的如果是NULL就不会进到这儿来了),并且由于source是多态的,那么source对象的第一个域是指向虚函数表的指针。
    2. src: source对象的类类型
    3. dat: destination对象的类类型
    4. 这个参数我也没弄清楚,但是当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;从内容可以看出这两个类型的定义也是不一样的

    1. $_ZTI1A的类型是__class_type_info
    2. $_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的使用场景

    1. dynamic_cast用来实现指针类型的转化。
    2. dynamic_cast如果转化成功则返回指向目标类型的指针,如果转换不成功返回NULL。
    3. dynamic_cast要求src必须是多态的,因为dynamic_cast需要从类的虚函数表表中获得类类型信息。
    4. dynamic_cast最常见的用法是从一个抽象基类转换到具体的实现类。

    当一个父类有多种子类时,如果目前有一个指向父类的指针,但是我们不知道指向父类的指针实际上指向的是哪一种子类,可以使用dynamic_cast<Child *>来判断,如果返回不是NULL,说明这是一个指向Child子类的指针,否则就不是。

    相关文章

      网友评论

        本文标题:C++ RTTI的dynamic_cast函数

        本文链接:https://www.haomeiwen.com/subject/hdzcfxtx.html