跳轉到內容

更多 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

已知用途

[編輯 | 編輯原始碼]
[編輯 | 編輯原始碼]

參考文獻

[編輯 | 編輯原始碼]
華夏公益教科書