9

仮想関数を持つクラス、または仮想関数を持つクラスから派生したクラスの場合、コンパイラーは2つのことを実行することを知っています。まず、そのクラスの仮想テーブルを作成し、次に、オブジェクトのベース部分に仮想ポインター(vptr)を配置します。実行時に、このvptrが割り当てられ、オブジェクトがインスタンス化されると正しいvtableをポイントし始めます。

私の質問は、インスタンス化プロセスのどこでこのvptrが設定されるのかということです。このvptrの割り当ては、コンストラクターの前/後のオブジェクトのコンストラクター内で発生しますか?

4

5 に答える 5

10

これは厳密に実装に依存します。

ほとんどのコンパイラでは、

コンパイラーは、各コンストラクターのMemberInitializerリスト内でthis->__vptrを初期化します。

アイデアは、各オブジェクトのvポインターがそのクラスのvテーブルを指すようにすることであり、コンパイラーはこのための非表示のコードを生成し、コンストラクターコードに追加します。何かのようなもの:

Base::Base(...arbitrary params...)
   : __vptr(&Base::__vtable[0])  ← supplied by the compiler, hidden from the programmer
 {

 }

このC++FAQは、正確に何が起こるかの要点を説明しています。

于 2011-10-28T20:32:09.737 に答える
9

vtableへのポインターは、階層内の各コンストラクターに入ると更新され、次に各デストラクタに入ると更新されます。vptrは基本クラスを指し始め、さまざまなレベルが初期化されると更新されます。

これは実装で定義されていることを多くの人から読んでいますが、これはvtableの選択全体であるためですが、実際には、すべてのコンパイラがvtableを使用し、vtableアプローチを選択すると、標準ではそのタイプがランタイムオブジェクトは、実行中のコンストラクタ/デストラクタのオブジェクトです。つまり、動的ディスパッチメカニズムが何であれ、構築/破壊チェーンをトラバースするときに調整する必要があります。

次のコードスニペットについて考えてみます。

#include <iostream>

struct base;
void callback( base const & b );
struct base {
   base() { callback( *this ); }
   ~base() { callback( *this ); }
   virtual void f() const { std::cout << "base" << std::endl; }
};
struct derived : base {
   derived() { callback( *this ); }
   ~derived() { callback( *this ); }
   virtual void f() const { std::cout << "derived" << std::endl; }
};
void callback( base const & b ) {
   b.f();
}
int main() {
   derived d;
}

標準では、そのプログラムの出力は、、、、であることが義務付けられていますがbasederived関数derivedへの4つの呼び出しすべてからbaseの呼び出しcallbackは同じです。実装できる唯一の方法は、構築/破棄の進行に合わせてオブジェクトのvptrを更新することです。

于 2011-10-28T20:46:54.457 に答える
1

このmsdnの記事では、詳細について説明しています。

そこにそれは言う:

「そして最終的な答えは...あなたが期待するようにです。それはコンストラクターで起こります。」

コンストラクターの最初に、コンストラクターにある他のコードが実行される前に追加する場合があります。


ただし、クラスAと、Aから派生したクラスA1があるとします。

  • 新しいAオブジェクトを作成すると、vptrはAクラスのコンストラクターの先頭に設定されます。
  • ただし、新しいオブジェクトA1を作成する場合:

「これが、クラスA1のインスタンスを作成するときの一連のイベント全体です。

  1. A1::A1はA::Aを呼び出します
  2. A::AはvtableをAのvtableに設定します
  3. A::Aが実行されて返されます
  4. A1 :: A1は、vtableをA1のvtableに設定します
  5. A1 :: A1が実行され、「
于 2014-03-24T14:16:54.017 に答える
0

this実装に依存しますが、C ++仕様(12.7 / 3)に従って、コンストラクター本体のポインターを介して非静的クラスメソッドにアクセスすることが許可されているため、コンストラクター自体が評価される前に実際に発生する必要があります。 ..したがって、コンストラクターの本体を呼び出す前にvtableをセットアップする必要があります。そうしないと、thisポインターを介した仮想クラスメソッドの呼び出しが正しく機能しません。thisポインターとvtableは2つの異なるものですが、C ++標準ではコンストラクターの本体でポインターを使用できるという事実は、thisコンパイラーが標準に準拠したvtableを実装する方法を示しています。this少なくともタイミングの観点から、正しく機能するためのポインタ。コンストラクター本体の呼び出し中または呼び出し後にvtableが初期化された場合、thisポインターを使用してコンストラクター本体内の仮想関数を呼び出すかthis、動的ディスパッチに依存する関数にポインターを渡すと、問題が発生し、未定義の動作が発生します。

于 2011-10-28T20:33:08.100 に答える
0

コンストラクターの本体では、仮想関数が呼び出される可能性があるため、実装でが使用されている場合は、vptrすでにvptr設定されています。

ctorで呼び出される仮想関数は、そのコンストラクターのクラスで定義されたものであり、より派生したクラスによってオーバーライドされる可能性のあるものではないことに注意してください。

#include <iostream>

struct A
{
    A() { foo (); }
    virtual void foo () { std::cout << "A::foo" << std::endl; }
};

struct B : public A
{
    virtual void foo () { std::cout << "B::foo" << std::endl; }
};


int
main ()
{
    B b;      // prints "A::foo"
    b.foo (); // prints "B::foo"
    return 0;
}
于 2011-10-28T20:44:45.313 に答える