跳转到内容

更多 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 没有声明任何成员函数,因此它也避免了意外覆盖基类函数的问题。请注意,在上面所示的方法中,至关重要的是基类之间不冲突。也就是说,Base1Base2 是独立层次结构的一部分。

警告

对象标识问题似乎在不同编译器之间并不一致。空对象的地址可能相同也可能不同。例如,考虑以下情况。

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; }
};

firstsecond 成员函数返回的指针在某些编译器上可能相同,而在其他编译器上可能不同。有关更多讨论,请参见 StackOverflow

已知用途

[编辑 | 编辑源代码]
[编辑 | 编辑源代码]

参考文献

[编辑 | 编辑源代码]
华夏公益教科书