5

在过去的 5 年里,我一直在假设虚拟继承会破坏静态组合。

但现在我发现,仍然保持静态组合,只是有关正确实例位置的附加信息。这是正确的吗?

4

3 回答 3

21

非虚拟继承中的数据布局:

class Point2d {
    int x_, y_;
};

class Point3d : public Point2d {
    int z_;
};

点2d:

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

点3d:

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

Point3d 由 Point2d 和 Point3d 的成员静态组成。

虚拟继承下

使用对象内的偏移变量实现。

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

点3d:

+-----------------+
| 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_          |     |
+-----------------+   --+

顶点3d:

+------------------+   --+
| 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

在虚拟多重继承中,基类VertexPoint3d共享基类Point2dVertex3d. 非虚拟继承成员像往常一样布局。

虚拟多重继承的要点是所有的后代都Point3dVertex共享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
+------------------+   --+                     --+

参考:

  • Lippman: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当你改变界面中的 pimpl 时事情会发生变化?

于 2010-12-02T13:33:48.523 回答