2009-06-09 21 views
5

いつか、仮想関数呼び出しをテンプレート化できると思います(おそらくc.l.C++モデレート時)。私は次の行に何か試しました。仮想呼び出しは、純粋仮想メンバーのアドレスを使用します。それは合法ですか?

#include <iostream> 

template<class T, class FUN> 
void callVirtual(T& t, FUN f){ 
    (*t.*f)(); 
} 


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


struct Derived : public Base{ 
    void sayHi(){ 
     std::cout << "Hi!" << std::endl; 
    } 
}; 


void Test(){ 
    Base* ptr = new Derived; 
    callVirtual(ptr,&Base::sayHi); 
} 

int main() 
{ 
    Test(); 
    return 0; 
} 

Output: 
Hi! 

コンパイル時に純粋仮想基本メンバーメソッドのアドレスが与えられても、テンプレート化されたメソッドは実行時に正しいメソッドを呼び出します。 標準のC++では、純粋な仮想メンバーのアドレスを取得することは合法ですか?事前

EDIT-1で

ありがとう:は、私は質問の後半部分削除 'どのように動作しませんの?'。それが注目を集めているように見えます。

EDIT-2:私は司会++ c.l.cを検索し、このlinkhttp://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/5ddde8cf1ae59a0d)に出くわしました。標準はそれを制限していないので、コンセンサスはそうであるように思われます、それは勝利です。

EDIT-3: CodeProjectの記事(ovanesのおかげで)読んだ後、私は、コンパイラは、いくつかの魔法を行うことを考えています。仮想関数はvtable(コンパイラ固有)を介して実装されているため、仮想関数のアドレスを取ると、常にvtableのオフセットが得られます。使用される 'this'ポインタに応じて、適切な関数(アドレスがオフセットにある)が呼び出されます。私は、標準が何も言わないので、これを正当化する方法はわかりません!

答えて

0

私はそれが未定義だと思います。私は仕様で何も見つかりませんでした。

仮想メソッドは、vtableという概念を使用して実装されています。

コンパイラの実装に固有のことだと思います。私はそれが純粋な仮想であることが重要だとは本当に考えていません。それがまさに仮想的であれば同じことが起こります。

私はVisual Studio 2008でコードをコンパイルし、exeを解凍しました。 VS2008は、渡された 'this'ポインタを使用してvtableエントリにジャンプするサンク機能を作成します。

これは、callVirtualテンプレート関数の設定と呼び出しです。

push offset [email protected]@$B3AE ; void (__thiscall *)(Base *) 
lea  eax, [ebp+ptr] 
push eax    ; Base ** 
call [email protected]@@[email protected]@@[email protected]@[email protected]@Z ; callVirtual<Base *,void (Base::*)(void)>(Base * &,void (Base::*)(void)) 

だから、サンク関数に関数ポインタを渡す:[email protected]@$B3AE

; void __thiscall Base___vcall_(Base *) 
[email protected]@$B3AE proc near 
jmp  [email protected]@$B3AE ; [thunk]: Base::`vcall'{4,{flat}} 
[email protected]@$B3AE endp 

すべてのサンク機能がやっているが、実際のクラスのメソッドにジャンプするのvtableを使用しています。

+0

感謝。あなたは純粋な仮想へのメンバ関数ポインタを記述するC++標準のセクションを知っていますか?私は見つけようとしましたが、どんな具体的な詳細も解読できませんでした。不完全な型へのメンバポインタが許可されているので、純粋仮想へのメンバポインタも許可されているようです。 – Abhay

+0

質問を誤読しました。私は私の答えを更新します。 –

+0

+1あなたの編集された答えは、この質問に対する「答え」に近づいています:-)。私は、仮想関数(純粋かどうか)を取り上げ、それを呼び出すことは、実装の定義した言語の側面に向かっていると考えるようになっています。 – Abhay

1

確かです。あなたのコードは、単にこのような純粋仮想メソッドを呼び出すよりも違いはありません:

void Test() 
{ 
    Base* ptr = new Derived; 
    ptr->sayHi(); 
    delete ptr; 
} 

唯一の違いは、あなたが呼び出しを行うための別のメカニズムを持っているということです、そしてcallVirtualを通じて、この場合は()。

+0

ああ、この別のメカニズムは、純粋な仮想メンバ関数のアドレスを取ることを含みます。それは私が主に関心を持っていたものです。 – Abhay

1

Magnus Skog氏によると、テンプレート部分は実際には関係ありません。

(ptr->* &Base::sayHi)() 

が動作するようだが、sayHiは純粋仮想であるため、

ptr->Base::sayHi() 

は明らかにしません:何それはつまるところは、ということです。

は、仮想、または純粋な仮想機能のアドレスを取ったときに何が起こるかについての標準では見つかりませんでした。私はそれが合法であるかどうかはわかりません。 GCCとMSVCでも動作しますが、Comeauのオンラインコンパイラでも不平を言うことはありません。

編集

編集内容が言うように、それは、有効である場合でも、私はまだそれが何を意味するのか思ったんだけど。

sayHiは純粋ではない(つまり、Base::sayHiの定義が存在する)と単純に仮定すると、そのアドレスをとるとどうなりますか? Base :: sayHiのアドレス、またはvtableが指す関数のアドレスを取得しますか(この場合Derived :: sayHi)?

明らかに、コンパイラは後者を仮定しますが、なぜですか? ptr->Base::sayHi()を呼び出すと、基底クラスでsayHiを呼び出しますが、Base::sayHiのアドレスを取ることは私にそれが私には矛盾しそうですDerived::sayHi

のアドレスを提供します。私が行方不明のこの背後にいくつかの論理的根拠はありますか?

+0

私も混乱しています。 C++は私を困惑させ続けています:-( – Abhay

+0

"アドレスを取れば何が起こりますか?_"あなたはその名前を取る**。メンバーへのポインタはすべて名前に関するものです。 – curiousguy

1

次の記事では、C++でのメンバー関数ポインタ、その実装方法、および欠陥の場所について詳しく説明します。また、仮想メンバー関数ポインタなどを処理します。私はそれがあなたのすべての質問に答えると思う。

http://www.codeproject.com/KB/cpp/FastDelegate.aspx

また、C++でのデリゲートを実装する方法を示しており、何をして罠かもしれない落とし穴。

種類に関しては、

Ovanes

+0

優れた読書、ありがとう。私の答えが得られたように見えます(編集3参照)。 – Abhay