Fluent C++:Mixin類——CRTP的陽面

原文

既然我們已經清楚了CRTP的工作原理,那么讓我與你分享另一種涉及模板的技術,該模板是CRTP的補充:Mixin類。

我發(fā)現(xiàn)Mixin類很有趣,因為它們?yōu)镃RTP提供了另一種實現(xià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方法。 但是,重復調用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方法的實現(xiàn)中的this->。 沒有它,代碼將無法編譯。 確實,編譯器不確定在哪里聲明的print:即使在模板類Printable中聲明了它,從理論上講,也無法保證該模板類不會被特化并針對特定類型進行重寫,從而不會公開print方法 。 因此,C ++中會忽略模板基類中的名稱。

使用this->是將它們重新包含在調用它們的函數(shù)范圍內的一種方法。 還有其他方法也可以做到,盡管它們可能并不適合這種情況。 無論如何,你都可以在Effective C++ 的第43條中閱讀有關此主題的所有信息。

為了避免顯式指定模板參數(shù),我們使用一個推導它們的函數(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);

而相應的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)有類添加通用功能,但要權衡取舍。

以下是它們之間的不同點:

CRTP:

  • 影響現(xiàn)有類的定義,因為它必須繼承自CRTP,
  • 客戶代碼直接使用原始類,并從其擴展的功能中受益。

mixin類:

  • 保持原始類不變,
  • 客戶代碼不會直接使用原始類,而是需要將其包裝到mixin中才能使用擴展功能,
  • 即使沒有虛析構函數(shù),它也會從原始類繼承。你可以這么干,除非要通過指向原始類的指針多態(tài)刪除mixin類。

了解這些權衡之后,你可以選擇最適合給定情況的解決方案。

CRTP不僅限于此。如果你想了解更多信息,我已經為CRTP撰寫了一個系列,該系列已變得很火。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容