空基类优化
目的
优化空类数据成员的存储空间
别名
EBCO: Empty Base Class Optimization Empty Member Optimization
动机
大小为 0 的类在 C++ 中是不存在的. C++ 需要空类大小不为 0 以确保对象的标识. 例如下面的 EmptyClass
的大小就是非 0 的, 因为数组中每一个对象的标识都是唯一的. 如果 sizeof(EmptyClass)
的大小为 0, 指针算数就会失效. 一般情况下, 类似 EmptyClass
的类大小通常为 1.
1 2 |
|
当类似的类作为另一个类的数据成员时, 它的大小一般比 1 字节要大. 编译器通常 4 字节对齐来避免切割. 4 字节的空类对象只是占位符, 毫无用处. 避免浪费空间, 节省内存, 帮助对象更适应 CPU 缓存是非常有好处的.
解决方案&示例代码
在 C++ 中, 如果一个空类作为基类被继承时, 情况会和上面的有些区别. 编译器允许继承层次结构扁平化, 被继承的空基类不占用空间. 例如下面的代码中 sizeof(AnInt)
在 32 位架构中是 4 字节, sizeof(AnotherEmpty)
是 1 字节, 虽然这两个类都继承自 EmptyClass
1 2 3 4 5 |
|
EBCO 有效的利用了这个特性. 这并不是说简单, 天真的将数据成员的空类变成基类是可取的, 因为这可能会暴露原本需要对用户隐藏的接口. 例如下面的 EBCO 实现方式, 可能会有副作用: 类 Foo
的用户现在可以看到一些方法 (如果在 E1, E2 中存在的话), 虽然他们是私有继承而来的, 不可访问.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
一种实现 EBCO 的实用方法是: 将空的类成员组合到单一的存储结构扁平的成员中. 下面的模板 BaseOptimization
的前两个模板参数是用来实现 EBCO的. 使用 BaseOptimization
改写类 Foo
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
使用 EBCO 并没有改变类 Foo 的继承体系. 保证基类不会互相冲突, 这是至关重要的. 也就是说 Base1 与 Base2 是独立的集成体系中的一部分.
注意: 对象身份标识的问题, 不同的编译器处理起来是不相同的. 空对象的地址可能是相同的, 也可能是不同的. 例如: BaseOptimization 的成员函数 first 和 second 返回的指针, 在有些编译器上可能是相同的, 而有些编译器上可能是不同的. 更多的讨论请看这里:stackoverflow
已知的用途
boost::compressed_pair 使用了该惯用法, 来优化 pair 的存储空间 C++03 模拟实现 unique_ptr 也使用了该惯用法