Fluent C++:Mixin类——CRTP的阳面

原文

既然我们已经清楚了 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类:

  • 保持原始类不变,