C++ でポージング

http://www.rubyist.net/~matz/20051027.html#p02

を見てそりゃ Ruby の open class の方が強力だろう!って思った人は私だけじゃないと思うのですが、それはともかく C++ でポージングしましょう。

http://d.hatena.ne.jp/shinichiro_h/20060112#1137000855

との違いは、上記はオブジェクト一つの所属クラスを変えるという話で、今回はクラス自体をそもそも別のクラスに置きかえてしまうという話です。既にインスタンス化されているオブジェクトとかにも影響が出ます。

実装を考えてみると、前者はオブジェクトの持っている仮想関数テーブルへのポインタを入れ変えてやればよく、後者はクラスごとに存在している仮想関数テーブル自体を書きかえてやれば良いことがわかります。

となると実装はほとんど自明で面白くないのですが。

#include <stdio.h>

#include <sys/mman.h>
#include <unistd.h>

void become_writable(const void *addr) {
    long pagesize = (int)sysconf(_SC_PAGESIZE);
    char *p = (char *)((long)addr & ~(pagesize - 1L));
    mprotect(p, pagesize * 10L, PROT_READ|PROT_WRITE);
}

template <class Dst, class Src>
void pose() {
    Dst d;
    Src s;
    void **dvtbl = *(void ***)&d;
    void **svtbl = *(void ***)&s;
    become_writable(dvtbl);
    while (*dvtbl) {
        *dvtbl++ = *svtbl++;
    }
}

struct Base {
    virtual ~Base();
    virtual void func() =0;
};

struct DerivedOrig {
    virtual void func();
};

struct DerivedNew {
    virtual void func();
};

Base::~Base() {}

void DerivedOrig::func() {
    printf("original\n");
}

void DerivedNew::func() {
    printf("new\n");
}

int main() {
    DerivedOrig *d = new DerivedOrig();
    d->func();

    pose<DerivedOrig, DerivedNew>();
    d->func();
}

で実行結果は、

original
new

どう見てもクラスが変わっていますおつかれさまでした。

メモリ保護を外しましょうっていうことと、なんか仮想関数テーブルって別に 0 終端してなさそうな気がすることと、あと変化するのは仮想関数だけってことくらいに注意というか、特に最後の理由でまぁ明らかに実用性ありません。 virtual ついてない関数はもちろん、 virtual でもスタックに作ったオブジェクトのメソッドは変化しませんですはい。

こういうことしたいなら静的結合を捨てて Objective-C の世界に行けって話なんですよねぇ…

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h