5

私は過去5年間、仮想継承が静的構成を破壊することを想定して作業していました。

しかし、静的な構成がまだ維持されていることを発見しました。正しいインスタンスの場所に関する追加情報があります。これは正しいですか?

4

3 に答える 3

21

非仮想継承のデータ レイアウト:

class Point2d {
    int x_, y_;
};

class Point3d : public Point2d {
    int z_;
};

ポイント 2d:

+--------------+
| int x_       |
+--------------+
| int y_       |
+--------------+

Point3d:

+--------------+   --+
| int x_       |     |
+--------------+     +-- Point2d subobject
| int y_       |     |
+--------------+   --+
| int z_       |
+--------------+

Point3d は、Point2d と Point3d のメンバーで静的に構成されます。

仮想継承中

オブジェクト内のオフセット変数で実装されます。

class Point3d : public virtual Point2d {
    int z_;
};

Point3d:

+-----------------+
| int z_          |
+-----------------+
| Point2d* _vbase |   --> offset to Point2d subobject (2 in this case)
+-----------------+   --+
| int x_          |     |
+-----------------+     +-- Point2d subobject
| int y_          |     |
+-----------------+   --+

このコンテキストでのアクセスPoint3d* point3d->x_は (C++ 擬似コード) に変換されます。

(static_cast<Point2d*>(point3d) + point3d->_vbase)->x_

vtable 内のオフセット ポインターなど、仮想継承を実装するにはさまざまな方法があることに注意してください。これは、仮想継承を実装する 1 つの方法にすぎません。これを選択したのは、vtable を介したインダイレクションではより多くの ASCII 描画が必要になるためです。

ここでは仮想継承には利点がありません。(コメントで @Matthieu が指摘したように) コンパイラがこのクラスを最適化して、内部データ レイアウトが非仮想継承と同じになるようにすることを期待します。仮想継承は、多重継承でのみ有益です (Vertex3d以下のクラスを参照)。

これは多重継承ではどのように見えるでしょうか?

 class Vertex : virtual Point2d {
     Vertex* next_;
 };

 class Vertex3d : public Point3d, public Vertex {
 };

バーテックス:

+-----------------+
| Vertex* next_   |
+-----------------+
| Point2d* _vbase |   --> offset of Point2d subobject (2 in this case)
+-----------------+   --+
| int x_          |     |
+-----------------+     +-- Point2d subobject
| int y_          |     |
+-----------------+   --+

Vertex3d:

+------------------+   --+
| int z_           |     |
+------------------+     +-- Point3d subobject
| Point2d* _vbase1 |     |--> offset to Point2d subobject (4 in this case)
+------------------+   --+
| Vertex* next_    |     |
+------------------+     +-- Vertex subobject 
| Point2d* _vbase2 |     |--> offset to Point2d subobject (2 in this case)
+------------------+   --+
| int x_           |     |
+------------------+     +-- shared Point2d subobject
| int y_           |     |   both Point3d and Vertex point to this 
+------------------+   --+   single copy of Point2d

仮想多重継承では、基本クラスVertexとの両方Point3dが の基本を共有Point2dVertex3dます。非仮想継承メンバーは、通常どおり配置されます。

仮想多重継承のポイントは、 と のすべての子孫が のPoint3d1Vertexつのコピーを共有することですPoint2d。仮想多重継承 (= "通常の" 多重継承)がなければPoint3d、 のサブオブジェクトとVertexサブオブジェクトの両方Vertex3dが の独自のコピーを持つことになりますPoint2d:

Vertex3d仮想多重継承なしのレイアウト:

+------------------+   --+
| int z_           |     |
+------------------+     +-- Point3d subobject --+
| int x_           |     |                       |
+------------------+     |                       +-- Point2d subobject
| int y_           |     |                       |   of Point3d
+------------------+   --+                     --+
| Vertex* next_    |     |
+------------------+     +-- Vertex subobject  --+
| int x_           |     |                       |
+------------------+     |                       +-- Point2d subobject
| int y_           |     |                       |   of Vertex
+------------------+   --+                     --+

参考文献:

  • リップマン: C++ オブジェクト モデルの内部。第3章
于 2010-12-02T13:33:38.963 に答える
3

仮想継承を使用するクラスのオブジェクトには、コンパイル時に決定される固定メモリレイアウトがあります。ただし、仮想ベースにアクセスするには、派生ポインターとの相対位置がわからないため、ある程度の間接参照が必要です。

ウィキペディアを参照してください

于 2010-12-02T13:14:05.773 に答える
0

頭が悪いのかもしれませんが、「静的合成」の意味がわかりません。あなたは pimpl がそれを壊すと言うので、そこから始めて、ポリモーフィズムと仮想継承を取り除きましょう。

次のコードがあるとします。

#include <iostream>
using namespace std;

class Implementation
{
public:
    bool do_foo() { return true; }
};

class Implementation2
{
public:
    bool do_foo() { return false; }
private:
    char buffer_[1024];
};

class Interface
{
public:
    Interface(void* impl) : impl_(impl) {};
    bool foo() { return reinterpret_cast<Implementation*>(impl_)->do_foo(); }
    void change_impl(void* new_impl) { impl_ = new_impl; }

private:
    void* impl_;
};

int main()
{
    Implementation impl1;
    Implementation2 impl2;

    Interface ifc(&impl1);
    cout << "sizeof(ifc+impl1) =  " << sizeof(ifc) << "\n";

    Interface ifc2(&impl2);
    cout << "sizeof(ifc2+impl2) =  " << sizeof(ifc2) << "\n";

    ifc.change_impl(&impl2);
    cout << "sizeof(ifc+impl2) =  " << sizeof(ifc) << "\n";

    cout << "sizeof(impl) = " << sizeof(impl1) << "\n";
    cout << "sizeof(impl2) = " << sizeof(impl2) << "\n";

}

「静的な構成を壊す」と言うときsizeof、インターフェイスのピンプルを変更すると物事が変化するという意味ですか?

于 2010-12-02T13:33:48.523 に答える