TOP MAP UP

C++ 仮想関数について

 C++でよく問題になるのが「クラスのメンバ関数のポインタを渡したい」という問題である(具体的にはWin32のある関数のコールバック関数にしたいとか)

 別に単純にポインタを渡すだけならなんぼでも出来るんだけど、それはthiscallであって、前述のコールバック関数ではおそらくグローバル関数と同列のポインタをプログラマは期待しているはずだ。しかしクラスを使う限り、thiscallから逃れられる事は決してない(後述を除く)

 実際の話、C++のクラスはグローバル関数+構造体なので(単純に使う場合)クラスを使わずその方法を使えば、関数はグローバルで書く事になるので、一気に問題は解決するが、あまりにそれは力技過ぎる気もする。

 しかし「自分が作るクラス間でなら、クラスごとの関数のポインタを相互に利用可能」というC++の機能がある。これでなんとかしのいでみよう(あなたの持っている問題がこれで解決出来れば良いのだが)



 C++には仮想関数というものがあり、これを使うと(もちろん処理系によって違うのだろうが)V tableという物がクラスに付加される(これがポイント)このV tableがある事で、dynamic_castというものが使える(dynamic_castはC++の機能なので説明は省く)



#include 

class A           { public: virtual t(); virtual ~A(); };
class B :public A { public:         t(); virtual ~B(); };
class C :public A { public:         t(); virtual ~C(); };
class D :public A { public:         t(); virtual ~D(); };

A::t(){ printf("A\n"); }
B::t(){ printf("B\n"); }
C::t(){ printf("C\n"); }
D::t(){ printf("D\n"); }

/*デストラクタは省略*/

void main(void){
 A *p;
 B b;
 C c;
 D d;

 p=dynamic_cast(&b); p->t();
 p=dynamic_cast(&c); p->t();
 p=dynamic_cast(&d); p->t(); }



 どうだろう?ベースとなるクラス(A)を作れば、その下のクラス(B,C,D)は相互にメンバ関数をキャスト(クロスキャスト)する事ができる(もちろん関数の型が同じでなければならない)

 しかしながら「V tableを使うと遅くなる」という噂があるのだが・・・・・
 (気にするほどではないという噂もある)プロファイラで計ってみるべし!



 ちなみにベースのクラスを同じに出来ない場合は、この方法は適用できない(例:Win32ファンクションを相手にしたものなど)その場合は非常に危険極まりないが、インラインアセンブラで無理矢理アドレスを取得する。

 やり方は、オブジェクトが作成された後実行される関数内で(コンストラクタ等)取得したい関数と同じ型の(クラス等も当然合わせる)ポインタ変数(ローカル変数でよい)を作り、それにポインタを渡す。
 そのコードをニーモニックコードで見て(この時点でコンパイル>実行してる事になる)それを参照しつつ、ローカル変数から扱いやすい変数にインラインアセンブラで転送してやると良い。



 インラインアセンブラで最適化を考える時に、使用頻度の高い関数だと、クラスの為のthisポインタの設定だとか、スタックの設定等も無駄だと感じるときがある。
 VCには実は特殊な関数修飾 nakedがあり、それを使うことで前述のプロローグ、エピローグコードを省くことができる。エミュレータで良く使うメモリアクセス関数の参考例を次に示す。

#pragma warning(push,1)
__declspec(naked) unsigned char MEMORY::_r1(unsigned char *addr,unsigned long offset)
{ _asm{ mov eax,dword ptr [ecx+184h]
        mov edx,dword ptr [esp+08h]
        mov al,byte ptr [eax+edx]
        ret 8                       } }
#pragma	warning(pop)

 ちなみに途中に入るpragmaはC的に見ると、リターン値がないのでコンパイル出来ないので、一時的に制約を落としてある。
 いうまでもないが、ecxにはthisポインタが入っている。ebpでなくespでアクセスしてるのもわざとである。
 なお、ここでは省いてあるが、おおもとの等価なCのコードはコメントアウトで残しておいた方が良い。理由はクラスの中身が変われば、thisポインタでのアクセスが滅茶苦茶になる可能性があるからだ。