CRTP是一個C++慣用法。Jim Coplien在1995年,早期的C++模板編程中將其命名為CRTP。
如果你在自己的代碼中使用過CRTP,那么你肯定知道它的用處。 在這種情況下,你可能知道本系列文章中的大部分內容(盡管你可能想快速看一下第2節,只是為了確保我們步調一致),并且你可以跳到第3節,在那里我提出了CRTP的通用幫助器,在代碼中使用它時,它確實很有幫助。
CRTP,第1節:定義
CRTP,第2節:CRTP可以為你的代碼帶來什么
CRTP,第3節:CRTP實現助手
CRTP是什么
CRTP包含了一下幾點:
- 繼承自模板類
- 使用派生類本身作為基類的模板參數
這是它在代碼中的樣子:
template <typename T>
class Base
{
...
};
class Derived : public Base<Derived>
{
...
};
這樣做的目的是在基類中使用派生類。 從基礎對象的角度來看,派生對象本身就是它自己,僅僅是做了向下轉型而已。 因此,基類可以通過將自身static_cast到派生類中來訪問派生類。
template <typename T>
class Base
{
public:
void doSomething()
{
T& derived = static_cast<T&>(*this);
use derived...
}
};
請注意,與典型的對派生類的轉換相反,我們在此不使用dynamic_cast。 當你要在運行時確保要轉換為的派生類正確時,可以使用dynamic_cast。 但是這里我們不需要這種保證:基類被設計為可以通過其模板參數繼承,而不能通過其他任何繼承。 因此,將其作為假設,并且static_cast就足夠了。
可能出錯的地方
如果兩個類恰好是從同一個CRTP基類派生的,那么當CRTP嘗試使用錯誤的類時,我們很可能會遇到未定義的行為:
class Derived1 : public Base<Derived1>
{
...
};
class Derived2 : public Base<Derived1> // bug in this line of code
{
...
};
Marek Kurdej在評論部分中提出了一種防止這種情況的解決方案。 它包括在基類中添加私有構造函數,并使基類與模板參數類成為友元:
template <typename T>
class Base
{
public:
// ...
private:
Base(){};
friend T;
};
實際上,派生類的構造函數必須調用基類的構造函數(即使你沒有在代碼中顯式地編寫它,編譯器也會盡力做到這一點)。 由于基類中的構造函數是私有的,因此除友元類外,沒有人可以訪問它。 唯一的友元類別是…模板類別! 因此,如果派生類與模板類不同,則代碼編譯失敗。 整潔吧?
CRTP的另一個風險是,派生類中的方法將會隱藏基類中的同名方法。 如Effective C ++ 33條中所述,其原因是這些方法不是virtual的。 因此,要注意不要在基類和派生類中使用相同的名稱:
class Derived : public Base<Derived>
{
public:
void doSomething(); // 這隱藏了基類中的doSomething方法
};
第一次看到CRTP時,我的最初反應是:“等等,我沒聽懂”。 然后我又看了幾次就明白了。 因此,如果你不了解它的工作原理,只需重新閱讀一下第1節,然后就應該這樣做(如果它不只是取得聯系,我很樂意與您討論) 。
實話實說,我首先寫了一篇關于CRTP的大型博客文章,我認為要完全閱讀它可能會令人生畏。 因此,我決定將其分為幾個邏輯部分,這些邏輯部分構成了本系列的劇集。 這篇文章比較短,但是有必要設置基礎知識。
下一節:CRTP有什么用 (Fluent C++:CRTP可以為你的代碼帶來什么)
。