空基类的理解
一、空基类的要素
1、类中不包含虚函数,只能有非虚函数和静态函数;
2、类中不存在任何非静态变量(可以有静态变量,静态变量内存不计入到类中);
3、如果该类继承了其他类,那么继承方式不可以是虚继承,且基类要满足条件1、2.
二、sizeof(空类)!=0的必要性
**在C++中,每个对象至少要占用一个自己字节。**假设存在一个对象,占用字节为0,那么对该对象的访问与C++的底层逻辑是有冲突的,没有内存,何来地址,也必然无法访问。
因此,空基类的默认大小为1。
三、空基类的优化问题
在不同的编译器中对于空基类存在不同的优化方式,其主要表现在继承方式。
本文章主要讨论MSVC编译器、G++编译器(MinGw)。
#include <iostream>
#pragma pack(1)
class A1 {};
class A2 {};
class A3 {};
class A4 {};
class B1 { int a; };
class B2 { int a; };
class B3 { int a; };
class C1 : public A1, B1{};
class C2 : public A1, A2, B1{};
class C3 : public A1, B1, A2{};
class C4 : public A1, A2, B1, A3{};
class C5 : public A1,A2,B1,A3,A4{};
#pragma pack()
int main(void)
{
std::cout << sizeof(A1) << std::endl;
std::cout << sizeof(B1) << std::endl;
std::cout << sizeof(C1) << std::endl;
std::cout << sizeof(C2) << std::endl;
std::cout << sizeof(C3) << std::endl;
std::cout << sizeof(C4) << std::endl;
std::cout << sizeof(C5) << std::endl;
return 0;
}
如上图代码所示,A相关类都是空基类,B相关类存在四字节内存。此处使用了“#pragma pack(1)"设置1字节对齐,为了使结果更明显。
MSVC编译器运行结果:
G++编译器运行结果:
通过相同代码在不同编译器的基础上编译运行的结果,可以得出一个结论:
在G++(MinGw)编译器中,编译器会对被继承的空基类进行优化,即所有被继承的空基类,在派生类中表现大小为0.
在MSVC编译器中,编译器会对单个被继承的空基类进行优化,单个空基类大小表现为0;而当继承多个“连续 ”的空基类时,第一个空基类表现为0,后续表现为1。
MSVC下类C内存分布主要为:
C1 = 0+4;
C2 = 0+1+4;
C3 = 0+4+0;
C4 = 0+1+4+0;
C5 = 0+1+4+0+1;
当然MSVC中针对多继承空基类优化的原理暂时还没清楚,目前的规律是这样,后面了解后再进行补充。
以下为参考内容:---------------------------------------------------------------------------------------------------------
EBO 在 C++ 中代表 “Empty Base Optimization”(空基类优化)。这是一种优化技术,允许编译器在特定条件下减少由于继承空基类(即没有数据成员的类)而可能产生的内存开销。
在 C++ 中,类可以用来定义数据结构和行为,但并不是所有的类都包含数据。有时,开发者会定义空的基类来利用多态、接口定义或是特征标记等特性。按照一般的对象布局规则,即使是空的基类也会占用一定的空间(通常是为了保持对象的唯一地址),这可能导致继承自空基类的派生类对象比预期的更大。
为了解决这个问题,C++ 标准允许编译器实施空基类优化,当一个类继承自一个或多个空基类时,编译器可以选择不为这些空基类分配额外的内存空间。这意味着,如果你有一个空的基类和一个从该基类继承的派生类,派生类的实例可能不会因为继承空基类而增加任何额外的内存开销。
EBO 的条件和限制标准兼容性:
·EBO 是由 C++ 标准允许的优化,但不是所有的编译器都实现了这一优化。大多数现代 C++ 编译器(如 GCC 和 Clang)都支持 EBO。
多重继承:在多重继承的情况下,编译器也可以应用 EBO,但具体的优化效果可能会因编译器的不同而有所差异。
非空基类:如果基类包含至少一个数据成员,那么它就不是空的,EBO 就不会应用于这种情况。
虚拟继承:虚拟继承引入了额外的复杂性,可能会影响 EBO 的应用。
考虑以下代码示例:
class EmptyBase {};
class Derived : public EmptyBase {
int value;
};
int main()
{
std::cout << "Size of EmptyBase: " << sizeof(EmptyBase) << std::endl;
std::cout << "Size of Derived: " << sizeof(Derived) << std::endl;
return 0;
}
在没有 EBO 的情况下,Derived 类的大小可能大于 int 类型的大小。但是,如果编译器应用了 EBO,Derived 类的大小将仅由其非静态数据成员(在这个例子中是一个 int 类型的成员)决定,因此 Derived 的大小可能与一个单独的 int 类型相同。
详情可参照:cppreference