39

First of all, I want to make myself clear that I do understand that there is no notion of vtables and vptrs in the C++ standard. However I think that virtually all implementations implement the virtual dispatch mechanism in pretty much the same way (correct me if I am wrong, but this isn't the main question). Also, I believe I know how virtual functions work, that is, I can always tell which function will be called, I just need the implementation details.

Suppose someone asked me the following:
"You have base class B with virtual functions v1, v2, v3 and derived class D:B which overrides functions v1 and v3 and adds a virtual function v4. Explain how virtual dispatch works".

I would answer like this:
For each class with virtual functions(in this case B and D) we have a separate array of pointers-to-functions called vtable.
The vtable for B would contain

&B::v1
&B::v2
&B::v3

The vtable for D would contain

&D::v1
&B::v2
&D::v3
&D::v4 

Now the class B contains a member pointer vptr. D naturally inherits it and therefore contains it too. In the constructor and destructor of B B sets vptr to point to B's vtable. In the constructor and destructor of D D sets it to point to D's vtable.
Any call to a virtual function f on an object x of polymorphic class X is interpreted as a call to x.vptr[f's position in vtables]

The questions are:
1. Do I have any errors in the above description?
2. How does the compiler know f's position in vtable (in detail, please)
3. Does this mean that if a class has two bases then it has two vptrs? What is happening in this case? (try to describe in a similar manner as I did, in as much detail as possible)
4. What's happening in a diamond hierarchy with A on top B,C in the middle and D at the bottom? (A is a virtual base class of B and C)

Thanks in advance.

4

3 に答える 3

37

1. 上記の説明に誤りはありますか?

すべて良い。:-)

2. コンパイラはどのようにして vtable 内の f の位置を認識しますか?

各ベンダーには独自の方法がありますが、私は常に vtable をメモリ オフセットへのメンバー関数シグネチャのマップと考えています。したがって、コンパイラはこのリストを維持するだけです。

3. これは、クラスに 2 つのベースがある場合、2 つの vptr があることを意味しますか? この場合、何が起こっていますか?

通常、コンパイラは、指定された順序で一緒に追加された仮想ベースのすべての vtable と、仮想ベースの vtable ポインターで構成される新しいvtable を作成します。これに、派生クラスの vtable 関数が続きます。これは非常にベンダー固有ですがclass D : B1, B2、 の場合、通常は が表示されますD._vptr[0] == B1._vptr

多重継承

そのイメージは実際にはオブジェクトのメンバーフィールドを構成するためのものですが、vtables はコンパイラーによってまったく同じ方法で構成できます (私が理解している限り)。

4. A が B の上に、C が中央に、D が下にあるダイアモンド階層では、何が起こっていますか? (A は B と C の仮想基底クラスです)

簡単な答えは?絶対地獄。両方のベースを事実上継承しましたか?それらの1つだけですか?どっちもない?最終的には、クラスの vtable を構成するのと同じ手法が使用されますが、どのように行うべきかはまったく決まっていないため、これを行う方法は大きく異なります。ダイヤモンド階層の問題を解決するための適切な説明がここにありますが、これのほとんどと同様に、ベンダー固有です。

于 2010-10-19T21:07:53.873 に答える
5
  1. は、私にはよく見えますよ
  2. 実装固有ですが、ほとんどはソースコードの順序(つまり、クラスに表示される順序)で、基本クラスから始まり、派生クラスから新しい仮想関数を追加します。コンパイラーがこれを行う決定論的な方法を持っている限り、コンパイラーがやりたいことは何でも問題ありません。ただし、Windowsでは、COM互換のVテーブルを作成するには、ソース順にする必要があります

  3. (わからない)

  4. (推測)ひし形は、基本クラスBのコピーを2つ持つことができることを意味します。仮想継承はそれらを1つのインスタンスにマージします。したがって、D1を介してメンバーを設定すると、D2を介してそれを読み取ることができます。(CはD1、D2から派生し、それぞれがBから派生しています)。どちらの場合も、関数ポインタが同じであるため、vtableは同じになると思います。つまり、データメンバーのメモリがマージされます。
于 2010-10-19T20:50:41.510 に答える
1

コメント:

  • デストラクタが入ってくるとは思いません!

  • D d; d.v1();コンパイラはコンパイル/リンク時に関数アドレスを解決できるため、egなどの呼び出しはおそらくvtableを介して実装されません。

  • コンパイラfは、そこに配置されているため、の位置を認識しています。

  • はい、複数の基本クラスを持つクラスには通常、複数のvptrがあります(各基本クラスに仮想関数があると仮定します)。

  • スコットマイヤーズの「EffectiveC++」の本は、多重継承とダイアモンドを私よりもよく説明しています。この(そして他の多くの)理由でそれらを読むことをお勧めします。それらを必読と考えてください!

于 2010-10-19T20:50:02.897 に答える