(轉載)C++ CRTP 編程技巧
[編輯] [转简体] (简体译文)概要
藉助 CRTP,父類可以預先知道子類的存在。
正文
以下內容轉載自知乎回答(有刪改) https://www.zhihu.com/question/564819820/answer/2750198351
原問題:C++ 卡牌遊戲中 CardBase 類派生出許多子類,現在需要給每個子類實現 clone 方法,每個派生類的 clone 方法實現都很相似,因此不想寫許多重複的虛函數,怎麼辦?
回答:不想寫大量重複的虛函數,又想要實現不同的對象類型完成不同的動作,CRTP 是不錯的方法:
北車註:此處引用另一個回答所說:CRTP,奇特的遞歸模板模式(Curiously Recurring Template Pattern)是 C++ 的一種看起來很怪異的模板編程技巧。
template <typename ConcreteCard> class CardBase { public: ConcreteCard* clone() const { auto concrete_ptr = static_cast<const ConcreteCard*>(this); // 北車註:此處還是要每個派生類去實現相應的拷貝構造函數,只是「不想重複寫許多 clone 函數」的問題解決了。 // 看起來有點治標不治本,但是此處就只是看看它的 CRTP 是怎麼實現的吧! return new ConcreteCard(*concrete_ptr); } }; // class CardBase class CardA : public CardBase<CardA> {}; class CardB : public CardBase<CardB> {}; class CardC : public CardBase<CardC> {};
CRTP 的關鍵在於定義子類時將子類本身的類型作爲模板類型參數傳入基類模板。這相當於給基類注入了子類的類型信息,基類裏面定義的成員函數就可以把 this 轉換爲實際的子類的指針,然後執行需要子類的類型信息才能完成的動作(比如這裏的 clone)。子類不需要任何額外的代碼就能夠享受到之前通過虛函數才能得到的好處。
北車註:也就是通過 CRTP,父類可以預先知道子類的存在。
當然,這樣實現的話就沒法往同一個 std::vector 裏面塞各種種類的 card 了,因爲 CardA 、CardB 和 CardC 並沒有公共的基類。這個問題很好解決,我們只需要再利用一次繼承並將 clone 改造爲虛函數即可:
class AbstractCard { public: virtual ~AbstractCard() noexcept = default; virtual AbstractCard* clone() const = 0; }; // class AbstractCard template <typename ConcreteCard> class CardBase : public AbstractCard { public: AbstractCard* clone() const override { auto concrete_ptr = static_cast<const ConcreteCard*>(this); return new ConcreteCard(*concrete_ptr); } }; // class CardBase class CardA : public CardBase<CardA> {}; class CardB : public CardBase<CardB> {}; class CardC : public CardBase<CardC> {};
利用 AbstractCard 我們將 ConcreteCard 模板參數給擦掉,這樣就可以往同一個 std::vector 裏面塞 AbstractCard* 了:
std::vector<AbstractCard*> cloneCards(const std::vector<AbstractCard*>& cards) { std::vector<AbstractCard*> result; result.reserve(cards.size()); for (auto card : cards) { result.push_back(card->clone()); } return result; }
北車註:然而,這裏只是藉此問題記錄 CRTP 的用法。如果真要解決題主的問題,我感覺另一種方法更好,見:http://huidong.xyz/article.php?blog_id=475