C++ 虚拟继承的内存使用
我有一些类(大多数是抽象类,带有虚拟继承): 现在我使用它们:C++ 虚拟继承的内存使用,c++,inheritance,memory-management,virtual,multiple-inheritance,C++,Inheritance,Memory Management,Virtual,Multiple Inheritance,我有一些类(大多数是抽象类,带有虚拟继承): 现在我使用它们: B * object = new MyB(); object->f1(); //declared in A, imp. in MyA object->f3(); //declared in B, imp. in MyB 一切正常,代码对我来说“很好”(我可以快速从MyB切换到YourB,只需更改一行) 但我的问题是: 与下面列出的类似代码相比,它使用了多少额外内存(相同的结果,不同的结构) 我不擅长使用内存布局/vTa
B * object = new MyB();
object->f1(); //declared in A, imp. in MyA
object->f3(); //declared in B, imp. in MyB
一切正常,代码对我来说“很好”(我可以快速从MyB
切换到YourB
,只需更改一行)
但我的问题是:
与下面列出的类似代码相比,它使用了多少额外内存(相同的结果,不同的结构)
我不擅长使用内存布局/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(winx32,visualstudio原生编译器),但我不确定它在这里是否权威。也许这不算什么-我不认为两个样本都是100%相等的。这取决于实现,但通常虚拟继承需要:
- 每个虚拟基类的额外指针(或偏移):转换(例如)
到B*
的调整将取决于同一对象中的哪些其他子对象也从A*
虚拟派生。我认为这可以存储在vtable中,而不是对象本身,因此开销可以是每个类而不是每个对象A
- 构造函数和析构函数中的额外逻辑,用于确定虚拟基对象是否需要在该点初始化/销毁
- 一些指针转换的额外工作,读取存储的指针/偏移量,而不是应用编译时常量
对于内存使用,您可以通过打印
sizeof(MyB)
来测量实现中的每个对象开销。每个类的开销可能可以忽略不计,除非您有大量的类。这取决于实现,但通常虚拟继承需要:
- 每个虚拟基类的额外指针(或偏移):转换(例如)
到B*
的调整将取决于同一对象中的哪些其他子对象也从A*
虚拟派生。我认为这可以存储在vtable中,而不是对象本身,因此开销可以是每个类而不是每个对象A
- 构造函数和析构函数中的额外逻辑,用于确定虚拟基对象是否需要在该点初始化/销毁
- 一些指针转换的额外工作,读取存储的指针/偏移量,而不是应用编译时常量
对于内存使用,您可以通过打印
sizeof(MyB)
来测量实现中的每个对象开销。除非您有大量的类,否则每个类的开销可能可以忽略不计。在我开始之前,您的问题被称为过早优化。在使用大小和空间之前,还需要考虑其他问题:正确性和健壮性
继承通常通过虚拟函数表实现,本质上是函数的地址表。因此,额外的代码空间量取决于虚拟函数的数量
虚拟函数使用跳转表执行。通常的做法是使用函数表中的值加载程序计数器。这通常是两个装配说明
函数表的变量空间量可能小于结构中对齐填充所浪费的内存。浪费的执行时间小于调用函数的开销
编辑1:顺便说一句,汇编指令通常在1微秒或更短的时间内执行。因此,通过函数表调用需要2微秒。将此与等待磁盘I/O或用户I/O进行比较
这就是为什么它是一个过早的优化:在分析之前进行优化。在担心代码和变量空间的浪费之前,先分析整个代码并关注瓶颈 在我开始之前,您的问题被称为过早优化。在使用大小和空间之前,还需要考虑其他问题:正确性和健壮性 继承通常通过虚拟函数表实现,本质上是函数的地址表。因此,额外的代码空间量取决于虚拟函数的数量 虚拟函数使用跳转表执行。通常的做法是使用函数表中的值加载程序计数器。这通常是两个装配说明 函数表的变量空间量可能小于结构中对齐填充所浪费的内存。浪费的执行时间小于调用函数的开销 编辑1:
顺便说一句,汇编指令通常在1微秒或更短的时间内执行。因此,通过函数表调用需要2微秒。将此与等待磁盘I/O或用户I/O进行比较
这就是为什么它是一个过早的优化:在分析之前进行优化。在担心代码和变量空间的浪费之前,先分析整个代码并关注瓶颈 对于常用的编译器,开销大致如下
sizeof(object)
在两种情况下都返回4,因为您测量的是指向对象的指针的大小,而不是对象本身。无论指针指向哪个对象,指针大小都是相同的。对于常用的编译器,开销大致如下
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