vtblを使って無理矢理呼び出す(実用度0)

デストラクタの振る舞いが意外と反応あったので派生です。

C++には仮想関数を実現するために、
vptr、vtblというのがあるというのは先日書きました。書きました。書きました。

ポインタが4バイトの環境で、
多くの場合、

class A {
    int a;
public:
    virtual void f(){}
};
class B : public A {
    int b;
};
class C : public A {
    int c;
};

とあったならば、sizeof(C)は16という値を返す。
これはインスタンスが、
vptr
a
b
c
といったレイアウトを持っている事を示唆している。(順番は実装依存
i686-apple-darwin9-gcc-4.0.1
で試したところ、やはりvptrはインスタンスの先頭にあるようだ。
(レイアウトは保証されません

では、それを証明するコードを書いてみやう。

class A {
    int a;
public:
    A() : a(111) {}
    virtual void f() { printf("A::f\n"); }
    virtual void m() { printf("A::m\n"); }
    virtual void n() { printf("A::n\n"); }
};

class B : public A {
    int b;
public:
    B() : b(222) {}
    virtual void f() { printf("B::f\n"); }
    virtual void m() { printf("B::m\n"); }
};

class C : public B {
    int c;
public:
    C() : c(333) {}
    virtual void f() { printf("C::f\n"); }
};

int main()
{
    A* a = new C();
    void* vp = static_cast<void*>(a);
    int* ip = static_cast<int*>(vp);
    for(size_t i = 0; i < sizeof(C)/sizeof(int); ++i){
        printf("%d\n", ip[i]);
    }

これらを証明できたならば、
printfの結果は、
xxxx <- アドレスっぽい数値
111
222
333
のような結果を吐く。
では、vtblを取り出す。

    void* vvtbl = reinterpret_cast<void*>(ip[0]);
    int* vtbl = static_cast<int*>(vvtbl);
    void (*f1)() = (void (*)())vtbl[0];
    void (*f2)() = (void (*)())vtbl[1];
    void (*f3)() = (void (*)())vtbl[2];
    f1();
    f2();
    f3();
    delete a;
    return 0;
}

結果はどうなるだろう?
実際には

C::f
B::m
A::n

という結果を吐き出す。
この結果はvtblはそのインスタンス
その実クラス毎に適切な仮想関数を呼び出せるように
これらの関数のアドレスを持っていることに他ならない。
(メンバ関数を無理矢理void (*)()な関数ポインタにキャストしているが、
 これらは、同時にメンバ関数も第一引数にthisを伴う関数でしかない、
 ということを意味してる、と思いねえ)←明らかに不作法です

ちなみに
typedef void (A::*member_function)();
のsizeof(member_function)の結果は8になり、
メンバ関数を処理するためのvtblとは異なっている。
(上記のようにvtblはsizeof(int)と等しいものを持っていた)


故に、メンバ関数ポインタを型として扱った場合、
単なるポインタだけを含んでいるのではないようだ。
メンバ関数ポインタを操作するようにしているときに、
仮想関数を使っても問題ないように処理するようになっていると考えられる。


多重継承や仮想継承をしている際はさらに変わる可能性があり、
そこまでテストはしていなひ。


やっぱり、メンバ関数ポインタを効果的には使えるが、
vtblを使うのは実用性において問題がある、とかいっておきたいふじこ。