條款43:學習處理模板化基類內的名稱
know how to access names in templatizedbase classes
假設我們要寫乙個應用程式,它可以把訊息傳送到幾個不同的公司去,訊息既可以以加密方式也可以以明文(不加密)的方式傳送。如果我們有足夠的資訊在編譯期間確定哪個訊息將要傳送給哪個公司,我們就可以採用基於模板的解法:
class companya ;
class companyb ;
... // classes for other companies
class msginfo ; // 這個類用來儲存資訊,以便將來產生資訊
template
class msgsender
void sendsecret(const msginfo& info)
// 類似sendclear,唯一不同的是這裡呼叫c.sendencrypted
};這個做法行得通,但是假設我們有時需要在每次傳送訊息的時候把一些資訊記錄到日誌中。通過乙個派生類可以很簡單地增加這個功能,下面這個似乎是乙個合理的方法:
template
class loggingmsgsender: public msgsender
...
};注意派生類中的 資訊傳送函式的名字 (sendclearmsg) 與它的基類中的那個(sendclear)不同。這是乙個好的設計,因為它避免了遮掩基類中的名字和重新定義乙個繼承來的non-virtual函式。但是上面的**不能通過編譯,編譯器會抱怨sendclear不存在。
問題在於當編譯器遇到類模板loggingmsgsender的定義時,它們不知道它從哪個類繼承。當然它繼承的是 msgsender,但是 company 是乙個模板引數,這個直到更遲一些才能被確定(當 loggingmsgsender 被例項化的時候)。不知道 company 是什麼,就沒有辦法知道類msgsender是什麼樣子的。特別是,沒有辦法知道它是否有乙個 sendclear函式。
為了使問題具體化,假設我們有乙個堅持加密通訊的類companyz:
class companyz ;
msgsender模板不適用於 companyz,因為那個模板提供乙個sendclear函式,對於 companyz物件沒有意義。我們可以建立乙個msgsender針對companyz 的特化版本:
template<> // 乙個全特化的msgsender,刪除了sendclear
classmsgsender
};這個類定義開始處的"template <>"語法,它表示這是乙個用於模板引數為 companyz 時的 msgsender模板的特化版本。只要型別引數被定義成了 companyz,再沒有其他模板引數可供變化。
再次考慮派生類loggingmsgsender:
sendclear(info); // if company == companyz,這個函式不存在
就像注釋中寫的,當基類是msgsender時,這裡的**是無意義的,因為那個類沒有提供 sendclear函式。這就是為什麼 c++ 拒絕這個呼叫:它認識到基類模板可能被特化,而這個特化不一定提供和通用模板相同的介面。因此它通常會拒絕在模板化基類(msgsender)中尋找繼承來的名稱(sendclear)。在某種意義上,當我們從object-oriented c++ 跨越到 template c++時,繼承就不像以前那樣通行無阻了。
有三種方法可以解決此問題。
1. 在被呼叫的基類函式前面加上"this->":
this->sendclear(info);
2. 使用using宣告式:
usingmsgsender::sendclear; // 告訴編譯器,請它假設sendclear位於基類內
void sendclearmsg(const msginfo& info)
這裡的情形不是基類名字被派生類名字隱藏,而是如果我們不告訴它去做,編譯器就不會搜尋 基類域。
3. 顯式指定被呼叫的函式是在基類中的:
msgsender::sendclear(info);
但這往往是最不讓人滿意的乙個解法,因為如果被呼叫函式是 virtual,顯式限定會關閉 virtual繫結行為。
從名字可見性的觀點來看,這裡每乙個方法都做了同樣的事情:它向編譯器保證任何後繼的基類模板的特化都將支援通用模板提供的介面。所有的編譯器在解析乙個像loggingmsgsender 這樣的派生類模板時,這樣一種保證都是必要的,但是如果保證被證實不成立,真相將在後繼的編譯過程中暴露。例如,如果後面的源**中包含這些,
loggingmsgsenderzmsgsender;
msginfomsgdata;
... // 在msgdata內放置資訊
zmsgsender.sendclearmsg(msgdata); // error! won't compile
對 sendclearmsg 的呼叫將不能編譯,因為在此刻,編譯器知道基類是模板特化msgsender,它們也知道那個類沒有提供sendclearmsg試圖呼叫的 sendclear函式。
從根本上說,本條款討論的是編譯器是會早些(當派生類模板定義被解析的時候)診斷對基類成員的非法引用,而這就是為什麼當那些類被模板例項化的時候,它假裝不知道基類的內容。
·在派生類模板中,可以經由"this->" 字首,經由 using 宣告式,或經由乙個顯式基類限定引用基類模板中的名稱。
條款44:將與引數無關的**抽離template(1)
factor parameter-independent code out oftemplate
如果你不小心,使用模板可能導致**膨脹:重複的(或幾乎重複的)的**資料,或兩者都有的二進位製碼。結果會使源**看上去緊湊而整潔,但是目標**臃腫而鬆散,所以你需要了解如何避免這樣的二進位制擴張。
你的主要工具有乙個有氣勢的名字:共性與變性分析,但其概念非常平民化。
當你寫乙個函式,你意識到這個函式的實現的某些部分和另乙個函式的實現本質上是相同的,你會從這兩個函式中分離出通用的**,放到第三個函式中,並讓那兩個函式來呼叫這個新的函式。也就是說,你分析那兩個函式以找出那些共性與變性的構件,你把共性的構件移入乙個新的函式,並把變性的構件保留在原函式中。類似地,如果你寫乙個類,而且你意識到這個類的某些構件和另乙個類是相同的,你把會通用構件移入乙個新類中,然後使用繼承或復合使得原來的類可以訪問這些通用特性。
在寫模板時,你要做同樣的分析,而且用同樣的方法避免重複。模板中的重複是隱式的:僅有乙份模板源**,所以你必須培養自己去判斷在乙個模板被例項化多次後可能發生的重複。例如,假設你要為固定大小的正方矩陣寫乙個模板,支援逆矩陣運算。
template// 支援n×n矩陣,元素型別為t的物件
class squarematrix ;
這個模板取得乙個型別引數t,它還有乙個非型別引數size_t。非型別引數比型別引數更不通用,但是它們是完全合法的,而且,就像在本例中,它們可以非常自然。現在考慮以下**:
squarematrixsm1;
sm1.invert(); // call squarematrix::invert
squarematrixsm2;
sm2.invert(); // call squarematrix::invert
這裡將有兩個invert被例項化。這兩個函式不是相同的,因為乙個作用於 5 x 5 矩陣,而另乙個作用於 10 x 10矩陣,但是除了常數 5 和 10 以外,這兩個函式是相同的。這是乙個發生模板導致的**膨脹的經典方法。
我們的直覺是建立取得乙個值作為引數的函式版本,然後用 5 或10 呼叫這個引數化的函式以代替複製**。以下是對squarematrix的第一次修改:
template // 於尺寸無關的基類
class squarematrixbase ;
template
class squarematrix: private squarematrixbase // inline呼叫基類版的invert
}; 就像你能看到的,invert 的引數化版本是在基類squarematrixbase 中的。與 squarematrix 一樣,squarematrixbase 是乙個模板,但它引數化的僅僅是矩陣中的物件的型別,而沒有矩陣的大小。
squarematrixbase::invert 僅僅是乙個計畫用於派生類以避免**重複的方法,所以它是 protected而不是 public。呼叫它的額外成本應該為零,因為派生類的invert使用 inline函式呼叫基類版本(這個inline 是隱式的)。這些函式使用了 "this->" 標記,因為如果不這樣,在模板化基類中的函式名(諸如squarematrixbase)會被派生類隱藏。另外,squarematrix 和squarematrixbase 之間的繼承關係是private 的。這準確地反映了基類存在的理由僅僅是簡化派生類的實現的事實,而不是表示squarematrix和squarematrixbase之間的乙個概念上的 is-a 關係。
Effective C 讀書筆記 2
讓自己習慣c 條款1 視c 為乙個語言聯邦 c 可以看作是四種次語言組成的 c 包括區塊 語句 預處理器 內建資料型別 陣列 指標等 object oriented c 主要表現c 的面對物件的性質,包括類 封裝 繼承 多型性 virtual函式等 template c 為c 泛型程式設計部分 st...
《effective C 》讀書筆記
1,c 關鍵字explicit c 中,乙個引數的 建構函式 或者除了第乙個引數外其餘引數都有預設值的多參建構函式 承擔了兩個角色。1 是個 構造器,2 是個預設且隱含的型別轉換操作符 所以,有時候在我們寫下如 aaa 這樣的 且恰好 的型別正好是aaa單引數構造器的引數型別,這時候 編譯器就自動呼...
Effective C 讀書筆記
一 讓自己習慣c 1 條款01 視c 為聯邦語言 c 的組成可分為四部分 1.c c 仍然以c語言為基礎。區塊 語句 預處理 內建資料型別 陣列 指標等都來自c。2.object oriented c c with classes所訴說的 classes 包括構造和析構 封裝 繼承 多型 virtu...