仮想メンバー関数へのポインター。どのように機能するのでしょうか?
-
23-08-2019 - |
質問
次の C++ コードを考えてみましょう。
class A
{
public:
virtual void f()=0;
};
int main()
{
void (A::*f)()=&A::f;
}
推測する必要があるとすれば、この文脈での &A::f は、通常のメンバー関数へのポインターと仮想メンバー関数へのポインターの間に明示的な分離がないため、「A の f() 実装のアドレス」を意味すると思います。 。A は f() を実装していないため、コンパイル エラーになります。しかし、そうではありません。
それだけではありません。次のコード:
void (A::*f)()=&A::f;
A *a=new B; // B is a subclass of A, which implements f()
(a->*f)();
実際には B::f を呼び出します。
それはどのようにして起こるのでしょうか?
解決
ここでは、メンバー関数ポインターに関する情報が多すぎます。「The Well-Behaved Compilers」には仮想関数に関するものがいくつかありますが、記事は実際には C++ でのデリゲートの実装に関するものであったため、IIRC が記事を読んだときはその部分を流し読みしていました。
http://www.codeproject.com/KB/cpp/FastDelegate.aspx
端的な答えは、コンパイラに依存するということですが、可能性の 1 つは、メンバー関数ポインターが、仮想呼び出しを行う「サンク」関数へのポインターを含む構造体として実装されていることです。
他のヒント
標準はそれはそれが起こるべきかだと言うので、それは動作します。 GCC店は仮想テーブルはバイト単位で、当該の機能のオフセット、私はGCCとのいくつかのテストをしました、そして、それは仮想関数のために判明します。
struct A { virtual void f() { } virtual void g() { } };
int main() {
union insp {
void (A::*pf)();
ptrdiff_t pd[2];
};
insp p[] = { { &A::f }, { &A::g } };
std::cout << p[0].pd[0] << " "
<< p[1].pd[0] << std::endl;
}
これら2つの関数の仮想テーブルエントリのバイトオフセット - そのプログラムは1 5
を出力します。これは、のItaniumのC ++ ABI の、追従しますそのに指定します。
私は完全に一定ではないんだけど、私はそれだけで通常の多型の行動だと思います。私は&A::f
が実際にクラスのvtableの中に関数ポインタのアドレスを意味していることを考えると、あなたはコンパイラエラーを取得されていません理由です。 vtableの中にスペースがまだ割り当てられ、それはあなたが実際に戻ってきている場所です。
これは理にかなっています。これはあなたの第二の例では、なぜ(a->*f)()
作品である - f
は、派生クラスで実装されているのvtableを参照している