虚拟继承的内存使用情况

时间:2014-05-12 13:47:52

标签: c++ inheritance memory-management virtual multiple-inheritance

我有一些类(大多数是抽象的,虚拟继承):

class A{
    public:
        virtual void f1() = 0;
        virtual void f2() = 0;
};

class B : virtual public A{
    public:
        virtual void f3() = 0;
};

class MyA : virtual public A{
    public:
        virtual void f1(){ ... }
        virtual void f2(){ ... }
};

class MyB : virtual public B, virtual public MyA{
    public:
        void f3(){ ... }
};

现在我使用它们:

B * object = new MyB();
object->f1(); //declared in A, imp. in MyA
object->f3(); //declared in B, imp. in MyB

一切正常,代码是好的"对我来说(我可以快速从MyB切换到YourB只更改1行。)

但我的问题是:

它使用多少额外内存,与下面列出的类似代码相比(结果相同,结构不同)?

我对内存布局/ vTables不太满意,所以请以简单的方式向我解释 - 我想知道,如果我的应用程序将花费更多资源(内存)和如果可执行文件会更慢

我将该代码与那个代码进行比较:

class MyA{
    public:
        virtual void f1(){ ... }
        virtual void f2(){ ... }
};

class MyB : public MyA{
    public:
        void f3(){ ... }
};

MyB * object = new MyB();
object->f1(); //declared in MyA, imp. in MyA
object->f3(); //declared in MyB, imp. in MyB

sizeof(object)在两个示例中都返回4(Win x32,Visual Studio本机编译器),但我不确定它是否具有权威性。也许它不算数 - 我不认为两个样本都是100%相等。

3 个答案:

答案 0 :(得分:2)

这取决于实现,但通常需要虚拟继承:

  • 每个虚拟基类的额外指针(或偏移量):要转换(例如)B*A*的调整将取决于同一对象中的哪些其他子对象也从{ {1}}。我认为这可以存储在 vtable 中,而不是存储在对象本身中,因此开销可能是每个类而不是每个对象。
  • 构造函数和析构函数中的额外逻辑,用于确定虚拟基础对象是否需要在该点初始化/销毁。
  • 一些指针转换的额外工作,读取存储的指针/偏移量而不是应用编译时常量。

对于内存使用,您可以通过打印A来衡量实现中的每个对象开销。除非你有大量的课程,否则任何每班的开销都可能微不足道。

答案 1 :(得分:1)

在开始之前,您的担忧被称为过早优化。在尺寸和空间之前还需要担心其他问题:正确性和稳健性。

继承通常通过虚函数表实现,实质上是函数的地址表。因此额外代码空间的数量取决于虚函数的数量。

使用跳转表执行虚函数。通常的做法是使用功能表中的值加载程序计数器。这通常是2个装配说明。

函数表的可变空间量可能小于结构中对齐填充所浪费的内存量。浪费的执行时间小于调用函数的开销。

编辑1:
顺便说一下,汇编指令通常在1微秒或更短的时间内执行。所以通过函数表调用需要2微秒。将其与等待磁盘I / O或用户I / O进行比较。

这就是为什么它是一个过早的优化:在分析之前进行优化。在担心代码浪费和可变空间之前,先描述整个代码并注意瓶颈。

答案 2 :(得分:1)

对于常用的编译器,开销大致如下

  1. 内存开销:指向每个父类的 vtable 的额外指针。
  2. 运行时开销:每个虚拟方法调用都有间接性来查找实际类型的调用对象的方法的正确实现。
  3. sizeof(object)在两种情况下都会返回4,因为您正在测量指向对象的指针的大小,而不是对象本身。无论指向哪个对象,指针大小都是相同的。