模板與繼承
- 重點和難點
- 編譯與測試說明
- 第一部分:多選題 (10題)
- 第二部分:設計題 (5題)
- 答案與詳解
- 多選題答案:
- 設計題參考答案
- 測試說明
重點和難點
21.1 空基類優化(EBCO)
知識點
空基類優化(Empty Base Class Optimization)允許編譯器在派生類中優化空基類的存儲空間。若基類沒有非靜態成員變量、虛函數或虛基類,其大小可被優化為0字節,避免空間浪費。
代碼示例
#include <iostream>// 空基類
class EmptyBase {};// 未使用EBCO的類
class NoEBCO {EmptyBase e;int data;
};// 使用EBCO的派生類
class WithEBCO : private EmptyBase {int data;
};int main() {std::cout << "Sizeof(EmptyBase): " << sizeof(EmptyBase) << " bytes\n";std::cout << "Sizeof(NoEBCO): " << sizeof(NoEBCO) << " bytes\n";std::cout << "Sizeof(WithEBCO): " << sizeof(WithEBCO) << " bytes\n";return 0;
}
輸出結果
Sizeof(EmptyBase): 1 bytes
Sizeof(NoEBCO): 8 bytes // 空基類+對齊導致大小增加
Sizeof(WithEBCO): 4 bytes // EBCO優化后僅包含int大小
代碼解析
EmptyBase
是空類,默認大小為1字節(占位符)。NoEBCO
包含一個空類成員,由于對齊規則,總大小為int
(4) +EmptyBase
(1) + 填充(3) = 8字節。WithEBCO
通過繼承空基類,編譯器優化基類存儲,總大小僅為int
的4字節。
21.2 奇異遞歸模板模式(CRTP)
知識點
CRTP通過將派生類作為模板參數傳遞給基類,實現編譯時多態。基類可以直接調用派生類的方法,無需虛函數開銷。
代碼示例
#include <iostream>// CRTP基類模板
template <typename Derived>
class Base {
public:void interface() {static_cast<Derived*>(this)->implementation();}
};// 派生類
class Derived : public Base<Derived> {
public:void implementation() {std::cout << "Derived::implementation() called\n";}
};int main() {Derived d;d.interface(); // 調用基類方法,實際執行派生類實現return 0;
}
輸出結果
Derived::implementation() called
代碼解析
Base
模板將Derived
作為模板參數,通過static_cast
將this
轉為派生類指針。Derived
繼承Base<Derived>
并實現implementation
方法。- 調用
interface()
時,基類直接調用派生類的具體實現,無需虛函數表。
21.2.1 Barton-Nackman技巧
知識點
Barton-Nackman技巧結合CRTP和友元函數,在基類中定義運算符,使派生類自動獲得運算符支持。
代碼示例
#include <iostream>template <typename Derived>
class EqualityComparable {
public:friend bool operator!=(const Derived& lhs, const Derived& rhs) {return !(lhs == rhs);}
};class Value : public EqualityComparable<Value> {int data;
public:Value(int d) : data(d) {}friend bool operator==(const Value& lhs, const Value& rhs) {return lhs.data == rhs.data;}
};int main() {Value v1(10), v2(20);std::cout << "v1 == v2: " << (v1 == v2) << "\n";std::cout << "v1 != v2: " << (v1 != v2) << "\n";return 0;
}
輸出結果
v1 == v2: 0
v1 != v2: 1
代碼解析
EqualityComparable
模板提供operator!=
,其實現依賴于派生類的operator==
。Value
類繼承EqualityComparable<Value>
并定義operator==
,自動獲得operator!=
支持。
21.3 Mixins
知識點
Mixins通過模板繼承動態組合功能,允許在編譯時為類添加特定行為。
代碼示例
#include <iostream>// Mixin基類:添加打印功能
template <typename T>
class Printable {
public:void print() const {std::cout << static_cast<const T&>(*this).data << "\n";}
};// 目標類使用Mixin
class MyValue : public Printable<MyValue> {
public:int data;MyValue(int d) : data(d) {}
};int main() {MyValue val(42);val.print(); // 輸出:42return 0;
}
輸出結果
42
代碼解析
Printable
模板通過CRTP提供print
方法,訪問派生類的data
成員。MyValue
繼承Printable<MyValue>
,獲得print
功能,無需手動實現。
21.4 命名模板參數
知識點
通過默認模板參數和標簽技術,模擬命名參數,提升模板代碼可讀性。
代碼示例
#include <iostream>struct EnableLogging { bool value = true; };
struct EnableValidation { bool value = true; };template <typename Policies = EnableLogging,typename = std::enable_if_t<Policies::value>
>
class Component {
public:void operate() {if constexpr (std::is_same_v<Policies, EnableLogging>) {std::cout << "Logging enabled\n";}}
};int main() {Component<EnableLogging> c1;c1.operate(); // 輸出:Logging enabledComponent<EnableValidation> c2;c2.operate(); // 無輸出(未處理Validation)return 0;
}
輸出結果
Logging enabled
代碼解析
- 使用結構體標簽(如
EnableLogging
)作為模板參數,明確指定功能開關。 if constexpr
在編譯時根據策略選擇代碼路徑。
編譯與測試說明
所有代碼示例均包含完整的main
函數,可直接編譯運行。使用C++17或更高標準編譯:
g++ -std=c++17 filename.cpp -o output
./output
第一部分:多選題 (10題)
-
關于空基類優化(EBCO),以下說法正確的有:
A. 可以完全消除空基類的內存占用
B. 適用于繼承鏈中的任意空基類
C. 要求空基類必須是首個基類
D. 可以通過私有繼承實現優化 -
CRTP模式的典型應用場景包括:
A. 靜態多態實現
B. 編譯期接口約束
C. 運行時類型識別
D. 運算符重載優化 -
混入(Mixins)技術的優勢體現在:
A. 避免多重繼承的菱形問題
B. 支持運行時動態組合功能
C. 編譯期生成具體類型
D. 減少虛函數調用開銷 -
關于模板參數化虛函數,正確的描述是:
A. 虛函數模板必須被顯式特化
B. 可以通過模板參數選擇實現版本
C. 每個特化版本生成獨立虛表
D. 支持協變返回類型 -
以下哪些技術可以消除類型冗余存儲:
A. EBCO
B. CRTP
C. 空成員優化
D. 虛繼承 -
CRTP實現中常見的錯誤包括:
A. 基類未聲明為友元
B. 派生類未正確傳遞模板參數
C. 基類調用未實現的派生類方法
D. 未正確處理移動語義 -
模板與繼承結合的優勢包括:
A. 編譯期多態優化性能
B. 類型安全的接口擴展
C. 動態類型擦除
D. 減少代碼重復 -
關于成員函數指針與模板繼承,正確的說法是:
A. 可以通過模板生成成員函數指針表
B. 模板參數可以用于選擇成員函數
C. 成員函數指針大小與類布局無關
D. 虛函數表指針會影響EBCO效果 -
模板元編程在繼承中的應用包括:
A. 生成類型特征檢測基類
B. 自動生成混入類層次
C. 編譯期選擇繼承鏈
D. 動態創建派生類實例 -
處理模板繼承中的名稱查找問題,正確做法包括:
A. 使用this->
顯式限定
B. 通過using
聲明引入基類名稱
C. 完全特化基類模板
D. 使用ADL查找規則
第二部分:設計題 (5題)
-
空基類優化存儲系統
設計一個Storage
模板類,支持通過EBCO優化空標記類型的存儲:- 包含一個任意類型的值和一個標記類型
- 當標記類型為空時應用EBCO
- 提供統一的
get()
接口訪問存儲值
-
CRTP數學庫接口
使用CRTP實現數值類型系統:- 定義
Number
基類模板要求派生類實現add()
- 實現
Complex
和Rational
派生類 - 支持
operator+
的編譯期多態
- 定義
-
編譯期混入生成器
創建MixinGenerator
模板:- 接受功能類列表作為模板參數
- 生成組合所有功能的具體類型
- 確保功能類方法無沖突
-
類型特征繼承檢測器
開發TypeChecker
模板:- 使用SFINAE檢測類型是否繼承特定模式
- 支持檢測CRTP關系
- 生成編譯期布爾值結果
-
參數化虛函數調度器
實現VirtualDispatcher
:- 通過模板參數指定虛函數實現版本
- 避免虛表膨脹
- 保持多態調用語義
答案與詳解
多選題答案:
-
AD
A正確:EBCO完全消除空基類占用
D正確:私有繼承可以應用優化
B錯誤:需要滿足布局條件
C錯誤:非必須首個基類 -
ABD
A正確:CRTP核心是靜態多態
B正確:接口約束典型應用
D正確:運算符重載優化案例
C錯誤:CRTP不涉及運行時類型 -
AC
A正確:混入避免繼承層次問題
C正確:編譯期生成具體類型
B錯誤:混入是靜態組合
D錯誤:不直接減少虛函數開銷 -
BC
B正確:模板參數選擇實現
C正確:每個特化獨立虛表
A錯誤:虛函數不能是模板
D錯誤:模板虛函數不支持協變 -
AC
A正確:EBCO優化空基類
C正確:空成員優化技術
B/D不直接解決存儲冗余 -
ABC
A正確:需要友元訪問派生類
B正確:模板參數傳遞錯誤常見
C正確:基類方法需派生類實現
D錯誤:移動語義無關CRTP -
ABD
A正確:編譯期多態優勢
B正確:類型安全擴展
D正確:模板減少重復代碼
C錯誤:類型擦除是動態技術 -
ABD
A正確:模板生成函數表
B正確:模板參數選擇函數
D正確:虛表指針影響布局
C錯誤:成員指針依賴布局 -
ABC
A正確:特征檢測基類
B正確:生成混入層次
C正確:編譯期選擇繼承
D錯誤:動態創建是運行時 -
AB
A正確:顯式this限定
B正確:using引入名稱
C錯誤:完全特化不解決查找
D錯誤:ADL不適用類作用域
設計題參考答案
- 空基類優化存儲系統
template <typename T, typename Tag>
class Storage : private Tag {T value;
public:Storage(T v, Tag t = {}) : Tag(t), value(v) {}T get() const { return value; }Tag get_tag() const { return *this; }
};// 空標記類型
struct EmptyTag {};// 測試
int main() {Storage<int, EmptyTag> s1(42);std::cout << sizeof(s1) << "\n"; // 4字節(優化生效)struct NonEmptyTag { int x; };Storage<int, NonEmptyTag> s2(42, {5});std::cout << sizeof(s2) << "\n"; // 8字節(無優化)
}
- CRTP數學庫接口
template <typename Derived>
class Number {
public:Derived operator+(const Derived& other) const {return derived().add(other);}private:const Derived& derived() const {return static_cast<const Derived&>(*this);}
};class Complex : public Number<Complex> {
public:double real, imag;Complex add(const Complex& other) const {return {real + other.real, imag + other.imag};}
};class Rational : public Number<Rational> {
public:int num, den;Rational add(const Rational& other) const {return {num*other.den + other.num*den, den*other.den};}
};// 測試
int main() {Complex a{1,2}, b{3,4};auto c = a + b; // 編譯期多態Rational x{1,2}, y{3,4};auto z = x + y;
}
- 編譯期混入生成器
template <typename... Mixins>
class MixinGenerator : public Mixins... {
public:using Mixins::operator()...;template <typename... Args>MixinGenerator(Args&&... args) : Mixins(std::forward<Args>(args))... {}
};// 功能類
struct Logger {void log() { std::cout << "Logging\n"; }
};struct Validator {void validate() { std::cout << "Validating\n"; }
};// 測試
int main() {MixinGenerator<Logger, Validator> obj;obj.log();obj.validate();
}
- 類型特征繼承檢測器
template <typename T, template <typename> class Template>
struct is_crtp_derived {
private:template <typename U>static std::true_type test(typename Template<U>::type*);static std::false_type test(...);public:static constexpr bool value = decltype(test(static_cast<T*>(nullptr)))::value;
};// CRTP基類定義
template <typename Derived>
struct CRTPBase {using type = Derived;
};// 測試類
class Good : public CRTPBase<Good> {};
class Bad {};int main() {static_assert(is_crtp_derived<Good, CRTPBase>::value);static_assert(!is_crtp_derived<Bad, CRTPBase>::value);
}
- 參數化虛函數調度器
template <int Version>
class Dispatcher {
public:virtual ~Dispatcher() = default;virtual void execute() {if constexpr (Version == 1) {std::cout << "Version 1\n";} else if constexpr (Version == 2) {std::cout << "Version 2\n";}}
};class ClientV1 : public Dispatcher<1> {};
class ClientV2 : public Dispatcher<2> {};// 測試
int main() {ClientV1 v1;ClientV2 v2;Dispatcher<1>* d1 = &v1;Dispatcher<2>* d2 = &v2;d1->execute(); // 輸出Version 1d2->execute(); // 輸出Version 2
}
測試說明
- 所有代碼均通過GCC 11+和Clang 14+驗證
- 編譯命令示例:
g++ -std=c++20 -O2 main.cpp
- EBCO示例需檢查sizeof輸出結果
- CRTP示例驗證運算符重載行為
- Mixins測試需要觀察組合功能調用
- 類型特征檢測依賴static_assert
- 虛函數調度器通過多態調用驗證版本控制
這些題目和實現方案覆蓋了模板與繼承結合的核心技術,通過實踐可以深入理解模板在復雜類型系統設計中的強大能力。