更多 C++ 惯用法/空基优化
优化空类类型的成员数据的存储
- EBCO:空基类优化
- 空成员优化
空类在 C++ 中时常出现。C++ 要求空类具有非零大小,以确保对象标识。例如,下面的 EmptyClass
数组必须具有非零大小,因为由数组下标标识的每个对象都必须是唯一的。如果 sizeof(EmptyClass)
为零,指针运算将失效。通常,此类类的尺寸为 1。
class EmptyClass {};
EmptyClass arr[10]; // Size of this array can’t be zero.
当同一个空类作为其他类的成员出现时,它可能会占用超过一个字节。编译器会根据体系结构要求,根据成员大小对数据进行对齐。填充字节是为了满足这些要求而插入的,但没有其他用途。避免内存浪费通常是可取的,在某些情况下甚至至关重要:从空基类派生具有任何类型单一成员的结构,或包含空成员,通常会使其大小增加 2 倍,因为存在对齐要求。
C++ 对空类被继承时的处理方式有特殊豁免。编译器可以扁平化继承层次结构,使得空基类不会占用空间。例如,在下面的示例中,在 32 位体系结构上 sizeof(AnInt)
为 4,而 sizeof(AnotherEmpty)
为 1 字节,即使它们都继承自 EmptyClass
class AnInt : public EmptyClass
{
int data;
}; // size = sizeof(int)
class AnotherEmpty : public EmptyClass {}; // size = 1
EBCO 以系统化的方式利用了这种豁免。将空类从成员列表中移到基类列表中可能并不理想,因为这可能会暴露原本对用户隐藏的接口。例如,以下应用 EBCO 的方式将应用优化,但可能会产生不良副作用:函数的签名(如果 E1、E2 中有任何函数)现在对 Foo
类的用户可见(尽管他们无法调用它们,因为是私有继承)。
class E1 {};
class E2 {};
// **** before EBCO ****
class Foo {
E1 e1;
E2 e2;
int data;
}; // sizeof(Foo) = 8
// **** after EBCO ****
class Foo : private E1, private E2 {
int data;
}; // sizeof(Foo) = 4
使用 EBCO 的一种实际方法是将空成员合并成一个单一成员,从而扁平化存储。下面的模板 BaseOptimization
对其前两个类型参数应用 EBCO。上面的 Foo
类已经过重写以使用它。
template <class Base1, class Base2, class Member>
struct BaseOptimization : Base1, Base2
{
Member member;
BaseOptimization() {}
BaseOptimization(Base1 const& b1, Base2 const & b2, Member const& mem)
: Base1(b1), Base2(b2), member(mem) { }
};
class Foo {
BaseOptimization<E1, E2, int> data;
}; // sizeof(Foo) = 4
使用这种技术,Foo
类的继承关系没有发生变化。而且,由于 BaseOptimization
没有声明任何成员函数,因此它也避免了意外覆盖基类函数的问题。请注意,在上面所示的方法中,至关重要的是基类之间不冲突。也就是说,Base1
和 Base2
是独立层次结构的一部分。
警告
对象标识问题似乎在不同编译器之间并不一致。空对象的地址可能相同也可能不同。例如,考虑以下情况。
template <class Base1, class Base2, class Member>
struct BaseOptimizationWithFunctions : Base1, Base2
{
Member member;
BaseOptimizationWithFunctions() {}
BaseOptimizationWithFunctions(Base1 const& b1, Base2 const & b2, Member const& mem)
: Base1(b1), Base2(b2), member(mem) { }
Base1 * first() { return this; }
Base2 * second() { return this; }
};
由 first
和 second
成员函数返回的指针在某些编译器上可能相同,而在其他编译器上可能不同。有关更多讨论,请参见 StackOverflow
- boost::compressed_pair 利用这种技术来优化对的尺寸。
- C++03 对 unique_ptr 的仿真也使用这种惯用法。