C++RTTIのdynamic_cast関数

5973 ワード

この記事はC++RTTIの続きで、前にtypeid()オペレータを紹介しました.この記事ではRTTIのもう一つの概念、すなわちdynamic_を紹介します.cast. 比較typeid,dynamic_castは実際のプロジェクトで大量に使用されます.
まず、一言でdynamic_キャストは何に使うんだ?castは2つのクラスの間でタイプ変換を行い、すなわち1つのポインタタイプを別のタイプに変換します.この変換プロセスは実行時刻に変換されるのでdynamic_と呼ばれますcast(これに対してstatic_cast).dynamic_castの使用は、次の2つの条件を満たす必要があります.
  • 両クラス間に親子関係がない場合、sourceは多態でなければならない.
  • 親から子への変換であれば、親(source)もマルチステートである必要があります.

  • そうでない場合、コンパイラはエラーを報告します::41:error:cannot dynamic_cast 'pc' (of type 'class *') to type 'class *' (source type is not polymorphic)
    上記の説明を再説明します:dynamic_castの使用にはsourceがマルチステートである必要があります.例外は、サブクラスから親クラスへの変換です.もう一つ角度を変えて、実はdynamic_castの使用はsourceが多態でなければならない.例外的に中性子類から親類への変換は天然の行為であり、dynamic_は必要ないと考えているからだ.castですね、(Parent*)childのようです.コードを見てみましょう
    #include 
    #include 
    
    class A1 {};
    class A2 : public A1 {};
    
    void foo(A2 * pa2) {
        A1 * va1 = dynamic_cast(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
    

    コンパイラがpa 2をva 1に直接付与し、dynamic_に呼び出さなかったことがわかります.castは、コンパイラが親と子の関係やメンバー構成を明確に知っているため、マルチステートの問題もないため、コンパイラはタイプ変換作業を完了することができ、実際にはdynamic_キャストはstatic_にマッピングされましたcast使用.
    使用状況について議論した後、変換の結果を見てみましょう(sourceはいずれも多態であると仮定します):
  • sourceとdestに親子関係がない場合、2つのクラスが同じに定義されていてもNULLになります.
  • 子から親まで、前に言ったように必ず成功します.
  • 親から子へ、これもdynamic_castの最も一般的な状況は、親ポインタが対応するサブクラスタイプを実際に指しているかどうかに依存し、これが実際のサブクラスオブジェクトであるかどうかを示す.
  • #include 
    #include 
    
    class A {
    public:
        virtual ~A() {}
    };
    class B1: public A {}; 
    class B2: public A {}; 
    
    void foo(A * pa) {
        B1 * vb1 = dynamic_cast(pa);
        printf("%s
    ", 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
    

    b 1とb 2はいずれもAのインスタンスであるが、b 2はB 1のインスタンスではないため、b 1は変換に成功し、b 2は成功しないことがわかる.
    前にdynamic_について紹介しましたcastの使用シーンではdynamic_について説明しますcastはどのように働いていますか.コード説明の問題:
    #include 
    #include 
    
    class A {
    public:
        virtual ~A() {}
    };
    class B: public A {};
    
    void foo(A * pa) {
        B * vb = dynamic_cast(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には4つのパラメータが必要です.
    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(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)の2つのパラメータの値定義を見てみましょう.
    _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のタイプの場合$ZTI 1 A、パラメータdstのタイプの場合$ZTI1B;内容からこの2つのタイプの定義も異なることがわかります
  • $_ZTI 1 Aのタイプは_class_type_info
  • $_ZTI 1 Bのタイプは_si_class_type_info、はい_class_type_infoのサブクラス;サブクラスには、親クラスタイプへのポインタが含まれます:const_class_type_info* __base_type;

  • 参照/usr/include/c+/4.4.4/cxxabi.h
    これらのクラスのタイプ情報はすべて、アプリケーションが起動するとmain関数のエントリの前にグローバル変数に登録され、ユーザープログラムがアクセスできるようにします.
    最後にdynamic_をまとめますキャストの使用シーン
  • dynamic_castはポインタタイプの変換を実現するために使用されます.
  • dynamic_キャスト変換に成功した場合はターゲットタイプへのポインタを返し、変換に成功しなかった場合はNULLを返します.
  • dynamic_castはdynamic_castはクラスの虚関数テーブルからクラスタイプ情報を取得する必要があります.
  • dynamic_castの最も一般的な使い方は、抽象ベースクラスから特定の実装クラスに変換することです.

  • 親クラスに複数のシードクラスがある場合、現在親クラスを指すポインタがある場合、親クラスを指すポインタが実際にどのシードクラスを指しているのか分かりませんが、dynamic_を使用します.castは、NULLでなければChildサブクラスへのポインタであり、そうでなければそうではないと判断する.