C++ 標準規定:任何對象必須有唯一地址。因此,即使類內無非靜態數據成員、無虛函數、無虛基類(即「空類」),其 sizeof 也至少為 1 字節。但在繼承體系中,若基類為空,強制分配 1 字節會造成內存浪費。空基類優化 (Empty Base Optimization, EBO) 允許編譯器將空基類子對象的大小優化為 0,使其地址與派生類對象地址重合,從而節省空間。
標準兼容的編譯器需處理單繼承、多繼承、菱形繼承等 8 種典型組合(圖示省略,見原文):
完全支持 EBO 的編譯器應在所有情況下將空基類大小壓縮為 0。
compressed_pair 到策略類若類模板持有兩個可能為空的類型 T1, T2,直接作為成員會佔用空間。改為私有繼承即可啟用 EBO:
// 未優化:sizeof(EBO<Empty, Empty>) == 2
template<typename T1, typename T2> class EBO { T1 m_t1; T2 m_t2; };
// 優化後:sizeof(EBO<Empty, Empty>) == 1 (或 0 視編譯器對完整對象要求)
template<typename T1, typename T2> class EBO : private T1, private T2 { };
直接繼承無法處理:T1 為 int (非類類型)、T1 == T2 (同類型無法重複繼承)、或一方非空。需引入特化層 EBO_IMPL,配合類型萃取 (is_empty, is_same) 進行編譯期決策:
// 核心邏輯:根據 T1, T2 是否為空、是否相同,選擇繼承還是組合
template<typename T1, typename T2, bool Same, bool Empty1, bool Empty2>
struct EBO_IMPL; // 主模板未定義,強制特化
// Case: 不同類型,皆為空 -> 雙重繼承
template<typename T1, typename T2>
struct EBO_IMPL<T1, T2, false, true, true> : T1, T2 {};
// Case: 不同類型,T1 空,T2 非空 -> 繼承 T1,組合 T2
template<typename T1, typename T2>
struct EBO_IMPL<T1, T2, false, true, false> : T1 { T2 m_t2; };
// Case: 相同類型 (無法重複繼承) -> 退化為組合
template<typename T1, typename T2>
struct EBO_IMPL<T1, T2, true, false, false> { T1 m_t1; T2 m_t2; };
// ... 其他特化組合省略 ...
// 統一入口
template<typename T1, typename T2>
class EBO : public EBO_IMPL<T1, T2,
boost::is_same<T1, T2>::value,
boost::is_empty<T1>::value,
boost::is_empty<T2>::value> { };
boost::compressed_pairBoost 已完美封裝上述邏輯並處理邊界情況(如 is_empty 實現細節、引用類型、CV 資格等)。生產代碼請直接使用:
#include <boost/compressed_pair.hpp>
template<typename T1, typename T2>
class EBO : public boost::compressed_pair<T1, T2> {
// 獲得 first(), second() 訪問器,尺寸最優
};
std::pair, std::tuple, 迭代器、函數對象基類 (iterator_traits, binary_function) 广泛依賴 EBO 避免空基類膨脹。EBO 是 C++ 「零開銷抽象」哲學的典型體現。理解其觸發條件(空類、非虛繼承、非同類型重複繼承)與限制,並善用 boost::compressed_pair 或 C++20 的 [[no_unique_address]] 屬性,能讓你的高性能庫在內存佔用上更極致。