あめだまふぁくとりー

Boost.Graphとかできますん

デストラクタ内での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の代入が行われている.こういうところにもちょっとしたオーバヘッドがあるんだなと.
他のコンパイラは知らん