空基类优化基础
为保证同一类型的不同对象地址始终不同,要求任何对象或成员子对象的大小至少为 1 个字节,即使这个类为一个空类。
struct A {}; // 只包含类型成员、非虚函数或静态成员变量
int main()
{
DBG_LOG("%d", sizeof(A)); // 输出为1
}
struct A {};
struct Derived
{
int i;
A a;
};
int main()
{
DBG_LOG("%d", sizeof(Derived)); // 输出为8
}
当空类作为类成员时,由于空类占用一个字节,并且内存对齐之后则Derived则需要占用8个字节,存在内存浪费;
运用空基类优化之后:
struct A {};
struct Derived : A
{
int i;
};
int main()
{
DBG_LOG("%d", sizeof(Derived)); // 输出为4
}
则空类A不会占用额外的内存空间,内存空间上得到优化;
注:空基类优化可简称为EBO (empty base optimization)或者 EBCO (empty base class optimization)
空基类优化失效
如果首个非静态数据成员的类型与一个空基类的类型相同或者由该空基类派生,则会禁用空基类优化【3】:
struct Base {}; // empty class
struct Derived1 : Base
{
int i;
};
struct Derived2 : Base
{
Base c; // Base, occupies 1 byte, followed by padding for i
int i;
};
struct Derived3 : Base
{
Derived1 c; // derived from Base, occupies sizeof(int) bytes
int i;
};
int main()
{
// empty base optimization does not apply,
// base occupies 1 byte, Base member occupies 1 byte
// followed by 2 bytes of padding to satisfy int alignment requirements
static_assert(sizeof(Derived2) == 2*sizeof(int));
// empty base optimization does not apply,
// base takes up at least 1 byte plus the padding
// to satisfy alignment requirement of the first member (whose
// alignment is the same as int)
static_assert(sizeof(Derived3) == 3*sizeof(int));
}
通过clang -Xclang -fdump-record-layouts -std=c++17 -c TestEBO.cpp可以查看上述代码的内存布局如下:
*** Dumping AST Record Layout
0 | struct Base (empty)
| [sizeof=1, dsize=1, align=1,
| nvsize=1, nvalign=1]
*** Dumping AST Record Layout
0 | struct Derived2
0 | struct Base (base) (empty)
1 | struct Base c (empty) // Derive3的空基类优化失效,Base c的地址偏移为1
4 | int i // 地址填充对齐,所以int i的地址偏移为4
| [sizeof=8, dsize=8, align=4,
| nvsize=8, nvalign=4]
*** Dumping AST Record Layout
0 | struct Derived1
0 | struct Base (base) (empty)
0 | int i
| [sizeof=4, dsize=4, align=4,
| nvsize=4, nvalign=4]
*** Dumping AST Record Layout
0 | struct Derived3
0 | struct Base (base) (empty)
4 | struct Derived1 c // Derive3的空基类优化失效,Derive1 c的地址偏移为4
4 | struct Base (base) (empty)
4 | int i
8 | int i
| [sizeof=12, dsize=12, align=4,
| nvsize=12, nvalign=4]
反之,不是首个非静态数据成员的类型:
struct Base {}; // empty class
struct Derived1 : Base
{
int i;
};
struct Derived2 : Base
{
int i;
Base c;
};
struct Derived3 : Base
{
int i;
Derived1 c; // derived from Base, occupies sizeof(int) bytes
};
对应的内存布局为:
*** Dumping AST Record Layout
0 | struct Base (empty)
| [sizeof=1, dsize=1, align=1,
| nvsize=1, nvalign=1]
*** Dumping AST Record Layout
0 | struct Derived2
0 | struct Base (base) (empty)
0 | int i // Derived2空基类优化生效
4 | struct Base c (empty)
| [sizeof=8, dsize=5, align=4,
| nvsize=5, nvalign=4]
*** Dumping AST Record Layout
0 | struct Derived1
0 | struct Base (base) (empty)
0 | int i
| [sizeof=4, dsize=4, align=4,
| nvsize=4, nvalign=4]
*** Dumping AST Record Layout
0 | struct Derived3
0 | struct Base (base) (empty)
0 | int i // Derived2空基类优化生效
4 | struct Derived1 c
4 | struct Base (base) (empty)
4 | int i
| [sizeof=8, dsize=8, align=4,
| nvsize=8, nvalign=4]
C++20 空类优化
C++20提供了[[no_unique_address]] 属性提供了组合模式下空类优化:
struct Base {}; // empty class
struct Derived1
{
[[no_unique_address]]Base b;
int i;
};
int main()
{
static_assert(sizeof(Derived1) == sizeof(int));
}
Derived1 占用内存为4个字节,空类并未占用额外内存。
空基类应用解析(tuple)
在【4】中介绍了std::tuple的应用实践,而stuple实际也应用了空基类优化:
struct Base1 {}; // 空类
struct Base2 {}; // 空类
struct Base3 {}; // 空类
int main()
{
DBG_LOG("%d %d", sizeof(std::tuple),
sizeof(std::tuple));
}
// 输出为1 4
本节介绍tuple中如何应用EBO。
tuple的模板参数可以支持接收任意类型,熟悉可变模板参数的同学可以快速实现如下代码:
template
struct Tuple;
template
struct Tuple {
};
template
struct Tuple {
Head h;
Tuple t;
};
此时模板参数类型为空类时存在内存浪费;OK,下一步应用EBO优化得到:
template
struct Tuple;
template
struct Tuple {
};
template
struct Tuple : private Head, Tuple {
};
但Head可能为int或者final类等不可继承类型,因此引入TupleEle:
template::value && !std::is_final::value>
struct TupleEle;
template
struct TupleEle {
T value;
T& Get() { return value; }
};
template
struct TupleEle : private T {
T& Get() { return *this; }
};
template
struct Tuple;
template
struct Tuple {
};
template
struct Tuple: private TupleEle, private Tuple {
};
struct Empty {};
int main()
{
Tuple t{};
DBG_LOG("%d", sizeof(t)); // 4
}
此时如果送入重复类型,则重复继承了TupleEle,导致 派生类转换到基类存在歧义,因此进一步修改为:
template::value && !std::is_final::value>
struct TupleEle;
template
struct TupleEle {
T value;
T& Get() { return value; }
};
template
struct TupleEle : private T {
T& Get() { return *this; }
};
template
struct Tuple;
template
struct Tuple {
};
template
struct Tuple: private TupleEle, private Tuple {
};
得益于EBO继承关系,在实现Get(tuple)利用模板参数推导,可以在常量时间内获取对应元素,补充Get之后的完整代码如下:
template::value && !std::is_final::value>
struct TupleEle;
template
struct TupleEle {
template
TupleEle(U&& u) : value(std::forward(u)) {};
T& Get() { return value; }
private:
T value;
};
template
struct TupleEle : private T {
template
TupleEle(U&& u) : T(std::forward(u)) {};
T& Get() { return *this; }
};
template
struct Tuple;
template
struct Tuple {
};
template
struct Tuple: TupleEle, private Tuple {
template
Tuple(H&& h, Rest&&...rest) : TupleEle(std::forward(h)),
Tuple(std::forward(rest)...){}
template
friend decltype(auto) Get(Tuple& t);
};
template
T& GetIndex(TupleEle& te) { return te.Get(); }
template
decltype(auto) Get(Tuple& t) { return GetIndex(t); }
在GetIndex调用时通过模板参数推导,index确定,推导出对应T;
参考资料
【1】C++20高级编程
【2】C++ Templates
【3】https://en.cppreference.com/w/cpp/language/ebo
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net