在过去的 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 中的偏移指针,这只是实现虚拟继承的一种方法。我选择了这个,因为通过 vtables 间接需要更多的 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
将Vertex
共享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
当你改变界面中的 pimpl 时事情会发生变化?