C++RTTIのtypeid()オペレータ

4248 ワード

まずなぜtypeid()は関数ではなくオペレータと呼ばれているのでしょうか.答えは関数ではないからです.C/C++を熟知している子供靴はすぐに推測して、それは1つのマクロであるべきで、マクロが開いた後に1段のコードである可能性があって、あるいは別の関数を呼び出します;C++のコードライブラリをめくってもtypeidの定義は見つかりません.つまり、マクロでも関数でもありません.
これはC++内部で定義された演算記号であり、C++言語自体の特性に属し、ライブラリではないので、操作記号と呼ばれています.これはC/C++の別のオペレータsizeof()と同じ動作です.彼らはすべてコンパイラが翻訳を解釈する責任を負っており、実行時には彼らの影が見えず、コンパイル後のアセンブリコードには彼らの影が見つからない.
コードの例:
#include 
#include 
#include 

void foo() {
    long a = sizeof(int);
    const std::type_info & b = typeid(int);
    printf("size=%d, name=[%s]
", a, b.name()); } int main(int argc, char * argv[]) { foo(); return 0; }

実行結果:size=4, name=[i]主張するfoo()アセンブリコードを見ています.
_Z3foov:
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp
    movq    $4, -16(%rbp)
    movq    $_ZTIi, -8(%rbp)
    movq    -8(%rbp), %rax
    movq    %rax, %rdi
    call    _ZNKSt9type_info4nameEv
    movq    %rax, %rdx
    movq    -16(%rbp), %rax
    movq    %rax, %rsi
    movl    $.LC0, %edi
    movl    $0, %eax
    call    printf
    leave
    ret

これは完全なfoo()アセンブリコードで、私たちはその中の2つの命令に注目しています.
movq    $4, -16(%rbp)
movq    $_ZTIi, -8(%rbp)

この2つの命令に対応するソースコードは、次のとおりです.
long a = sizeof(int);
const std::type_info & b = typeid(int);

不思議なことに、生成されたアセンブリ命令は元の高級言語C++よりも簡単で、第1の命令はintの大きさ4を直接計算して変数aに与え、第2の命令も定数を直接変数bに与える.コンパイラは翻訳中にオペレータsizeofとtypeidに遭遇すると、この部分の計算機能を直接完成させ、結果を出力します.
また、生成されたアセンブリコードファイルに定数$_が見つかりません.ZTIiの定義、この定数定義はC++実行ライブラリの中にあるので、つまりC++はこれらの定義があることを知ってから直接使うことができます.すなわち,C++コンパイラが生成するターゲットファイルは,C++コンパイラが提供する機能関数の一部に依存する.
ここまで言うと、以前プロジェクトをしていたときに出会った問題を思い出すかもしれません.大きなプロジェクトが複数のチームで複数人で開発されたとき、それぞれのモジュールの検証に問題はありません.統合するときはいつもわけのわからないエラー、crashなど、原因はもちろんたくさんありますが、異なるチームが異なるコンパイラを使ってターゲットコードを生成し、ライブラリを実行するときは、これらのモジュールが統合されると、異なるコンパイラがそれぞれのライブラリを実行するニーズに一致しないため、不思議なエラーが発生する可能性があります.
次にtypeid()がどのように働いているかについて議論します.typeidの機能は,1つのオブジェクトのタイプ定義を得るために用いられ,これに基づいて2つのタイプが一致するか否かを判断する.
std::type_infoは/usr/include/c+/4.4.4/typeinfoで定義されたクラスで、このクラスにはメンバーconst char*_が1つしかありません.name、虚関数テーブルポインタstd::type_を追加infoの実際のメモリサイズは16です.
定義された2つの関数を見てください.const char* name() const;は、タイプの名前、すなわちメンバー変数_を返します.nameの値.bool operator==(const type_info& __arg) const;は、2つのタイプが同じかどうかを比較します.比較文字列ではありません.nameのコンテンツ値ではなく、2つのタイプの__を比較します.nameが同じメモリを指すかどうかは、もちろん同じメモリを指すのは必ずメモリ値が同じです.
if (typeid(TYPE1) == typeid(TYPE2)) {
  ...
}
else {
  ...
}

typeidがコンパイル時に決定されるタイプである以上、マルチステート環境では、変数ポインタの実際のタイプに基づいて返すのではなく、変数の宣言タイプを返すことに注意してください.
include 
#include 

class A1 {};
class A2 : public A1 {};

void foo(A1 * a1) {
   printf("a1=%s
", typeid(a1).name()); } int main(int argc, char * argv[]) { A1 * a1 = new A1(); A2 * a2 = new A2(); foo(a1); foo(a2); return 0; }

実行結果は
a1=P2A1
a1=P2A1

foo()が印刷された結果は、実際のパラメータa 1がA 1タイプのオブジェクトであるか、A 2タイプのオブジェクトであるかにかかわらず、A 1であることがわかる.
最後にclass A 1とA 2のtypeを見てみましょうinfoはどのように定義されていますか.関数foo()は次のとおりです.
_Z3fooP2A1:
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp
    movq    %rdi, -8(%rbp)
    movl    $_ZTIP2A1, %eax
    movq    %rax, %rdi
    call    _ZNKSt9type_info4nameEv
    movq    %rax, %rsi
    movl    $.LC0, %edi
    movl    $0, %eax
    call    printf
    leave
    ret

ただし$ZTIP 2 A 1はクラスA 1のtype_info定義情報.
    .weak   _ZTSP2A1
_ZTSP2A1:
    .string "P2A1"
    .weak   _ZTIP2A1
_ZTIP2A1:
    .quad   _ZTVN10__cxxabiv119__pointer_type_infoE+16
    .quad   _ZTSP2A1

_ZTIP 2 A 1の内容は2つのフィールドがあり、前にtype_infoには2つのメンバーがあり、1つ目はクラスtype_を指します.info虚関数テーブルのポインタ、2番目はタイプ文字列の名前を指すポインタです.
前述したタイプintのtype_info定義は生成されたターゲットファイルには見つかりません.intはシステムタイプなので、C++はすべての内蔵タイプのtype_をinfoはライブラリに定義され、ユーザーがカスタマイズしたタイプのtype_を整理します.infoは、データ型を定義するときに生成されます.
_ZNKSt9type_info 4 nameEveはtype_info::name()関数のコードです.関数name()はinlineタイプとして宣言されるため、アセンブリコードはここでも表示されます.
エンディングは実はtypeid()がプロジェクトをする過程で直接使用されることはめったにありません.もっと多くのアプリケーションシーンはdynamic_のようなC++内部で使用されていると思います.cast関数.