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

Effective C++ 中文版 第三版》讀書筆記

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

我們需要一個程序,傳送信息到不同的公司去。信息要不譯成密碼,要不就是未加工的文字。如果編譯期間我們有足夠信息來決定哪一個信息傳至那一家公司,就可以采用基于 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 的內容毫無所悉的緣故。

請記住:
可在 derived class template 內通過 “this->” 指涉 base class template 內的成員名稱,或藉由一個明白寫出的 “base class 資格修飾符” 完成。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

  • 很實用的編程英語詞庫,共收錄一千五百余條詞匯。 第一部分: application 應用程式 應用、應用程序app...
    春天的蜜蜂閱讀 1,390評論 0 22
  • 再讀高效c++,頗有收獲,現將高效c++中的經典分享如下,希望對你有所幫助。 1、盡量以const \enum\i...
    橙小汁閱讀 1,234評論 0 1
  • 導語: 如果你已經加入了iOS攻城獅隊伍,那么我們由衷地祝賀您正式成為一名終身學習的程序猿;有人覺得這句話...
    超人猿閱讀 2,318評論 3 19
  • Java基礎常見英語詞匯(共70個)['?bd?ekt] ['?:rientid]導向的 ...
    今夜子辰閱讀 3,326評論 1 34
  • 姓名:嚴露露 公司:寧波大發化纖有限公司 《六項精進》289期反省一組學員 【日精進打卡第13天】 【知~學習】 ...
    嚴露露閱讀 130評論 0 0