既然我们已经清楚了 CRTP的工作原理 ,那么让我与你分享另一种涉及模板的技术,该模板是CRTP的补充:Mixin类。
我发现Mixin类很有趣,因为它们为CRTP提供了另一种实现等效的方法,因此提供了不同的权衡。
CRTP的主要用途是为特定类添加通用功能。 Mixin类也这样做。
Mixin类是定义通用行为的模板类,通过继承你希望扩展其功能的类型来实现。
这儿有一个例子。 让我们上一个代表一个人的名字的类。 它具有名字和姓氏,并且可以使用特定格式打印出该名字:
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
到目前为止,还没有什么特别的,但是这儿有一个新的需求:我们需要能够连续多次打印此名称。
我们可以向Name类添加一个repeat方法。 但是,重复调用print方法的概念也可以应用于其他类,例如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方法的实现中的this->。 没有它,代码将无法编译。 确实,编译器不确定在哪里声明的print:即使在模板类Printable中声明了它,从理论上讲,也无法保证该模板类不会被特化并针对特定类型进行重写,从而不会公开print方法 。 因此,C ++中会忽略模板基类中的名称。
使用this->是将它们重新包含在调用它们的函数范围内的一种方法。 还有其他方法也可以做到,尽管它们可能并不适合这种情况。 无论如何,你都可以在Effective C++ 的第43条中阅读有关此主题的所有信息。
为了避免显式指定模板参数,我们使用一个推导它们的函数:
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
我们甚至可以改个名字来让代码更有表现力:
Name ned("Eddard", "Stark");
repeatedlyPrint(ned).times(10);
(我在这里改名称,只是为了跟之前的CRTP代码进行比较,在那里这些新名称并不适用。)
CRTP的反面
Mixin类涉及模板和继承的混合,以便将通用功能插入现有类。 这种感觉就像CRTP,不是吗?
Mixin类类似于CRTP,但是是反过来的。 实际上,我们的mixin类如下所示:
class Name
template<typename Printable>
struct RepeatPrint : Printable
repeatPrint(ned).repeat(10);
而相应的CRTP则看起来像这样:
template<typename Printable>
struct RepeatPrint
class Name : public RepeatPrint<Name>
ned.repeat(10);
实际上,这是使用CRTP的解决方案的完整实现:
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类提供了解决同一问题的两种方法:向现有类添加通用功能,但要权衡取舍。
以下是它们之间的不同点:
CRTP:
影响现有类的定义,因为它必须继承自CRTP,
客户代码直接使用原始类,并从其扩展的功能中受益。
mixin类:
保持原始类不变,