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类:

  • 保持原始类不变,
  • 客户代码不会直接使用原始类,而是需要将其包装到mixin中才能使用扩展功能,
  • 即使没有虚析构函数,它也会从原始类继承。你可以这么干,除非要通过指向原始类的指针多态删除mixin类。

了解这些权衡之后,你可以选择最适合给定情况的解决方案。

CRTP不仅限于此。如果你想了解更多信息,我已经为CRTP撰写了一个系列,该系列已变得很火。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容