デストラクタ内でのvptrの書き換え
Effective C++第3版の9項に「コンストラクタやデストラクタ内では決して仮想関数を呼び出さないようにしよう」とある.基底オブジェクト生成時に仮想関数を呼び出すと,その基底オブジェクトを型とした場合の仮想関数呼び出しとなる…!
このとき思ったのが「ctor,dtor内での仮想関数呼び出しは静的な呼び出しになるのかなー」ということ.しかし9項後半でctor内から,仮想関数を呼び出す非仮想関数を呼び出してもその基底型の仮想関数呼び出しになる.非仮想関数内の仮想関数呼び出しはほぼ常に動的な呼び出しになるわけだから前述の考えは明らかに間違い.ctorが呼び出されるごとにvptrが書き換わっていくわけだ.
とまぁここまではたいした問題もなかった.「あれ,でもdtorはどうなるんだ?」という疑問が.vptrが派生クラスのテーブルを指し示してたら基底クラスのdtor内では派生クラスの仮想関数呼ばれてしまうではないか!これを回避するために基底オブジェクトを破棄する際にまたvptrを書き換えているのか?という訳で見てみた↓
struct Base { Base() : a(0) { } virtual ~Base() { } }; struct Derived : Base { virtual ~Derived() { a = 4 } }; int main() { Derived d; }
上をg++ -Sでアセンブリコードを出力.
BaseとDerivedのdtorの一部↓
_ZN4BaseD2Ev: ; Baseクラスのdtor .LFB4: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 subl $24, %esp movl 8(%ebp), %eax movl $_ZTV4Base+8, (%eax) ; vptrをBaseクラスのvtblを指すように変更 movl $0, %eax andl $1, %eax testb %al, %al je .L2 movl 8(%ebp), %eax movl %eax, (%esp) call _ZdlPv _ZN7DerivedD2Ev: ; Derivedクラスのdtor .LFB8: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 subl $24, %esp movl 8(%ebp), %eax movl $_ZTV7Derived+8, (%eax) ; vptrをDerivedクラスのvtblを指すように変更 movl 8(%ebp), %eax movl $4, 4(%eax) movl 8(%ebp), %eax movl %eax, (%esp) call _ZN4BaseD2Ev ; Baseクラスのdtor呼び出し movl $0, %eax andl $1, %eax testb %al, %al je .L7 movl 8(%ebp), %eax movl %eax, (%esp) call _ZdlPv
と言う訳でctorだけでなくdtorが呼び出される際にもvptrの代入が行われている.こういうところにもちょっとしたオーバヘッドがあるんだなと.
他のコンパイラは知らん