私は過去5年間、仮想継承が静的構成を破壊することを想定して作業していました。
しかし、静的な構成がまだ維持されていることを発見しました。正しいインスタンスの場所に関する追加情報があります。これは正しいですか?
私は過去5年間、仮想継承が静的構成を破壊することを想定して作業していました。
しかし、静的な構成がまだ維持されていることを発見しました。正しいインスタンスの場所に関する追加情報があります。これは正しいですか?
class Point2d {
int x_, y_;
};
class Point3d : public Point2d {
int z_;
};
+--------------+
| int x_ |
+--------------+
| int y_ |
+--------------+
+--------------+ --+
| int x_ | |
+--------------+ +-- Point2d subobject
| int y_ | |
+--------------+ --+
| int z_ |
+--------------+
Point3d は、Point2d と Point3d のメンバーで静的に構成されます。
オブジェクト内のオフセット変数で実装されます。
class Point3d : public virtual Point2d {
int z_;
};
+-----------------+
| 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_ | |
+-----------------+ --+
+------------------+ --+
| 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
が の基本を共有Point2d
しVertex3d
ます。非仮想継承メンバーは、通常どおり配置されます。
仮想多重継承のポイントは、 と のすべての子孫が のPoint3d
1Vertex
つのコピーを共有することです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
+------------------+ --+ --+
仮想継承を使用するクラスのオブジェクトには、コンパイル時に決定される固定メモリレイアウトがあります。ただし、仮想ベースにアクセスするには、派生ポインターとの相対位置がわからないため、ある程度の間接参照が必要です。
ウィキペディアを参照してください
頭が悪いのかもしれませんが、「静的合成」の意味がわかりません。あなたは 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
、インターフェイスのピンプルを変更すると物事が変化するという意味ですか?