effective C++ 條款 43:學習處理模板化基類內的名稱

來源:互聯網
上載者:User

我們需要一個程式,傳送資訊到不同的公司去。資訊要不譯成密碼,要不就是未加工的文字。如果編譯期間我們有足夠資訊來決定哪一個資訊傳至那一家公司,就可以採用基於template的解法:

class CompanyA{
public:
    void sendCleartext(const std::string& msg);
    void sendEncrypted(const std::string& msg);
};
class CompanyB{
public:
    void sendCleartext(const std::string& msg);
    void sendEncrypted(const std::string& msg);
};
...                            //針對其他公司設計的classes
class MsgInfo{...}; //這個class用來儲存資訊,以備將來產生資訊
template<typename Company>
class MsgSender{
public:
    void sendClear(const MsgInfo& info)
    {
        std::string msg;
        根據info產生資訊;
        Company c;
        c.sendCleartext(msg);
    }
    void sendSecret(const MsgInfo& info)
    {
        ...;//調用c.sendEncrypted,類似sendClear
    }
};

這個做法行的通。但假設我們有時候想要在每次發送出資訊的時候志記(log)某些資訊。derived class可以輕易加上這樣的行為,那似乎是個合情理的解法:

template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
    void sendClearMsg(const MsgInfo& info)
    {
        將傳送前資訊寫至log;
        sendClear(info);
    }
};

sendClearMsg 避免遮掩“繼承而得的名稱”(條款33),避免重新定義一個繼承而得的non-virtual函數(條款36)。但上述代碼無法通過編譯,編譯器看不到sendClear。為什嗎?

問題在於,編譯器遇到class template LoggingMsgSender定義式時,並不知道它繼承什麼樣的class。因為MsgSender<Company>中的Company是個template參數,不到後來(當LoggingMsgSender被具現化)無法確切知道它是什麼。而如果不知道Company是什麼,就無法知道class MsgSender<Company>看起來是個什麼樣----更明確的說是沒辦法知道它是否有個sendClear函數。

為了讓問題具體化,假設有個class CompanyZ只是用加密通訊:

class CompanyZ{
public:
    void sendEncrypted(const std::string& msg);
};

一般性的MsgSender template對CompanyZ並不合適,因為那個template提供了一個sendClear函數(其中針對其型別參數Company調用了sendCleartext函數),而這對CompanyZ對象並不合理。與糾正這個問題,我們可以針對CompanyZ產生一個MsgSender特化版;

template<>                                            //一個全特化的
class MsgSender<CompanyZ>{                //MsgSender;它和一般template相同
public:                                                    //差別只在於它刪掉了sendClear
    void sendSecret(const MsgInfo& info)
    {
        ...
    }
};

注意class定義式最前頭“template<>”文法象徵這既不是template也不是標準class,而是個特化版的MsgSender template,在template實參是CompanyZ時被使用。這事模板全特化(total template specialization):template MsgSender針對類型CompanyZ特化了,而且其特化是全面性的,也就是說一旦型別參數被定為CompanyZ,再沒有其他template參數可供變化。

template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
    void sendClearMsg(const MsgInfo& info)
    {
        將傳送前資訊寫至log;
        sendClear(info);//如果Company==CompanyZ,這個函數就不存在
    }
};

那就是為什麼C++拒絕這個調用的原因:它知道base class template可能被特化,而那個特化版本可能不提供和一般屬性template相同的介面。因此它往往拒絕在templatized base class(模板化基類,MsgSender<Company>)內尋找繼承而來的名稱(本例的SendClear)。從Object Oriented C++跨進Template c++繼承就不想以前那般暢通無阻了。

我們必須令c++“進入templatized base classes觀察”。有三個辦法:

第一個辦法是base class函數調用動作之前加上“this->”:

template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
    void sendClearMsg(const MsgInfo& info)
    {
        將傳送前資訊寫至log;
        this->sendClear(info); //成立,假設sendClear將被繼承
    }
};

第二個辦法是使用using 聲明式:

template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
    using MsgSender<Company>::sendClear;    //告訴編譯器,請他假設sendClear位於base class內
    void sendClearMsg(const MsgInfo& info)
    {
        將傳送前資訊寫至log;
        sendClear(info); //成立,假設sendClear將被繼承
    }
};

這裡的using聲明式不是條款33中“base class名稱被derived class名稱遮掩”,而是編譯器不進人base class範圍尋找,於是我們通過using告訴它,請他這麼做。

第三個做法是,明白指出被調用的函數位於base class內:

template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
    void sendClearMsg(const MsgInfo& info)
    {
        將傳送前資訊寫至log;
            MsgSender<Company>::sendClear(info); //成立,假設sendClear將被繼承
    }
};

但這往往不是令人滿意的一個解法,因為如果被調用的是virtual函數,上述的明確資格修飾MsgSender<Company>::會關閉virtual綁定行為。

從名稱可視點的角度出發,上述每個解法做的事情都相同:對編譯器承諾“base class template的任何特化版本都將支援其一般化版本所提供的介面”。這樣一個承諾是編譯器在解析(parse)像LoggingMsgSender這樣的derived class template時需要的。但如果這個承諾最終未被實踐出來,往後的編譯器最終還是會給事實一個公道。例如,如果稍後的源碼內含這個:

LoggingMsgSender<CompanyZ> zMsgSender;
MsgInfo msgData;
zMsgSender.sendClearMsg(msgData);        //錯誤!無法通過編譯。

因為在那個點上,編譯器知道base class是個template特化版本MsgSender<CompanyZ>,而它們知道那個class不提供sendClear函數,而這個函數卻是sendClearMsg嘗試調用的函數。

根本而言,面對“指涉base class members”之無效的references,編譯器的診斷時間可能發生在早期(當解析derived class template的定義式時),也可能發生在晚期(當那些templates被特定之template實參具現化時)。C++的政策是寧願早診斷。這就是為什麼“當base classes從templates中被具現化時”它假設它對那base classes的內容毫無所悉的緣故。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.