既然我們已經(jīng)清楚了CRTP的工作原理赁咙,那么讓我與你分享另一種涉及模板的技術(shù),該模板是CRTP的補充:Mixin類瓣俯。
我發(fā)現(xiàn)Mixin類很有趣俊马,因為它們?yōu)镃RTP提供了另一種實現(xiàn)等效的方法,因此提供了不同的權(quán)衡敷存。
CRTP的主要用途是為特定類添加通用功能住拭。 Mixin類也這樣做。
Mixin類是定義通用行為的模板類,通過繼承你希望擴展其功能的類型來實現(xiàn)滔岳。
這兒有一個例子。 讓我們上一個代表一個人的名字的類挽牢。 它具有名字和姓氏谱煤,并且可以使用特定格式打印出該名字:
class Name
{
public:
Name(std::string firstName, std::string lastName)
: firstName_(std::move(firstName))
, lastName_(std::move(lastName)) {}
void print() const
{
std::cout << lastName_ << ", " << firstName_ << '\n';
}
private:
std::string firstName_;
std::string lastName_;
};
這兒是使用它的代碼段:
Name ned("Eddard", "Stark");
ned.print();
這會輸出:
Stark, Eddard
到目前為止,還沒有什么特別的禽拔,但是這兒有一個新的需求:我們需要能夠連續(xù)多次打印此名稱刘离。
我們可以向Name類添加一個repeat方法。 但是睹栖,重復(fù)調(diào)用print方法的概念也可以應(yīng)用于其他類硫惕,例如PhoneNumber類,也可以具有print()方法野来。
mixin類的想法是將通用功能隔離到其自己的類中恼除,使用要增加該功能的類型對該類進行模板化,并從該類型派生:
template<typename Printable>
struct RepeatPrint : Printable
{
explicit RepeatPrint(Printable const& printable) : Printable(printable) {}
void repeat(unsigned int n) const
{
while (n-- > 0)
{
this->print();
}
}
};
在我們的示例中曼氛,Name類將扮演Printable的角色豁辉。
注意repeat方法的實現(xiàn)中的this->。 沒有它舀患,代碼將無法編譯徽级。 確實,編譯器不確定在哪里聲明的print:即使在模板類Printable中聲明了它聊浅,從理論上講餐抢,也無法保證該模板類不會被特化并針對特定類型進行重寫,從而不會公開print方法 低匙。 因此旷痕,C ++中會忽略模板基類中的名稱。
使用this->是將它們重新包含在調(diào)用它們的函數(shù)范圍內(nèi)的一種方法努咐。 還有其他方法也可以做到苦蒿,盡管它們可能并不適合這種情況。 無論如何渗稍,你都可以在Effective C++ 的第43條中閱讀有關(guān)此主題的所有信息佩迟。
為了避免顯式指定模板參數(shù),我們使用一個推導(dǎo)它們的函數(shù):
template<typename Printable>
RepeatPrint<Printable> repeatPrint(Printable const& printable)
{
return RepeatPrint<Printable>(printable);
}
然后這兒是我們的客戶端代碼:
Name ned("Eddard", "Stark");
repeatPrint(ned).repeat(10);
輸出就變成了:
Stark, Eddard
Stark, Eddard
Stark, Eddard
Stark, Eddard
Stark, Eddard
Stark, Eddard
Stark, Eddard
Stark, Eddard
Stark, Eddard
Stark, Eddard
我們甚至可以改個名字來讓代碼更有表現(xiàn)力:
Name ned("Eddard", "Stark");
repeatedlyPrint(ned).times(10);
(我在這里改名稱竿屹,只是為了跟之前的CRTP代碼進行比較报强,在那里這些新名稱并不適用。)
CRTP的反面
Mixin類涉及模板和繼承的混合拱燃,以便將通用功能插入現(xiàn)有類秉溉。 這種感覺就像CRTP,不是嗎?
Mixin類類似于CRTP召嘶,但是是反過來的父晶。 實際上,我們的mixin類如下所示:
class Name
{
...
};
template<typename Printable>
struct RepeatPrint : Printable
{
...
};
repeatPrint(ned).repeat(10);
而相應(yīng)的CRTP則看起來像這樣:
template<typename Printable>
struct RepeatPrint
{
...
};
class Name : public RepeatPrint<Name>
{
...
};
ned.repeat(10);
實際上弄跌,這是使用CRTP的解決方案的完整實現(xiàn):
template<typename Printable>
struct RepeatPrint
{
void repeat(unsigned int n) const
{
while (n-- > 0)
{
static_cast<Printable const&>(*this).print();
}
}
};
class Name : public RepeatPrint<Name>
{
public:
Name(std::string firstName, std::string lastName)
: firstName_(std::move(firstName))
, lastName_(std::move(lastName)) {}
void print() const
{
std::cout << lastName_ << ", " << firstName_ << '\n';
}
private:
std::string firstName_;
std::string lastName_;
};
int main()
{
Name ned("Eddard", "Stark");
ned.repeat(10);
}
那么甲喝,CRTP還是mixin類?
CRTP和mixin類提供了解決同一問題的兩種方法:向現(xiàn)有類添加通用功能,但要權(quán)衡取舍铛只。
以下是它們之間的不同點:
CRTP:
- 影響現(xiàn)有類的定義埠胖,因為它必須繼承自CRTP,
- 客戶代碼直接使用原始類淳玩,并從其擴展的功能中受益直撤。
mixin類:
- 保持原始類不變,
- 客戶代碼不會直接使用原始類,而是需要將其包裝到mixin中才能使用擴展功能蜕着,
- 即使沒有虛析構(gòu)函數(shù)谋竖,它也會從原始類繼承。你可以這么干侮东,除非要通過指向原始類的指針多態(tài)刪除mixin類圈盔。
了解這些權(quán)衡之后,你可以選擇最適合給定情況的解決方案悄雅。
CRTP不僅限于此驱敲。如果你想了解更多信息,我已經(jīng)為CRTP撰寫了一個系列宽闲,該系列已變得很火众眨。