Effective C++ 讀書筆記12(41~42)

7 模板和泛型編程

條款41:瞭解隱式接口和編譯器多態

面向對象編程世界總是以顯式接口和運行期多態解決問題:

class Widget{
public:
	Widget();
	virtual ~Widget();
	virutal std::size_t size() const;
	virtual void normalize();
	void swap(Widget& other);
	//...
};

有這樣一個函數:
void doProcessing(Widget& w){
	if(w.size() > 10 && w != someNastyWidget){
		Widget temp(w);
		temp.normalize();
		temp.swap(w);
	}
}

現在我們可以這樣說w:1.w這是一個顯式接口。
2.Widget的某些成員函數是virtual,w對這些函數的調用將表現出運行期多態。
Template及泛型編程的世界,與面向對象有根本上的不同。此時,顯式接口和運行期多態仍然存在,但重要性降低。反倒是隱式接口和編譯器多態移到了前頭。如果把上述函數變成函數模板:
template<typename T>
void doProcessing(T& w){
	if(w.size() >10 && w != someNastyWidget){
		T temp(w);
		temp.normalize();
		temp.swap(w);
	}
}

現在可以這麼說w:
1.  w必須支持哪一種接口,繫有template中執行於w身上的操作來決定。本例來看w的類型T好像必須支持size,normalize和swap成員函數、copy構造函數、不等比較。我們很快會看到這並非完全正確,但對目前而言足夠真實。重要的是,這一組表達式便是T必須支持的一組隱式接口。
2.凡涉及w的任何函數調用,例如operator>和operator!=,有可能造成template instantiated,使這些調用得以成功。這樣的instantiated行爲發生在編譯期。以不同的template參數instantiated function templates,會導致調用不同的函數,這便是所謂的編譯期多態
通常顯式接口由函數的簽名式(函數名,參數類型,返回類型)構成:
class Widget{
public:
	Widget();
	virtual ~Widget();
	virutal std::size_t size() const;
	virtual void normalize();
	void swap(Widget& other);
	//...
};

隱式接口就完全不同了。它並不基於函數簽名式,而是由有效表達式(valid expression)組成。例如上述模版函數中T(w的類型)的隱式接口看來好像有這些約束:
1.它必須提供一個名爲size的成員函數,該函數返回一個整數值。
2.它必須支持一個operator!=函數,用來比較兩個T對象。
但是並非如此:(以下沒看懂)
真要感謝操作符重載帶來的可能性,這兩個約束都不需要滿足。
同樣的道理,T並不需要支持operator!=。

當人們第一次以此種方式思考隱式接口,大多數會感到頭疼。但是,隱式接口僅僅是一組有效表達式構成,表達式自身可能看起來很複雜,但它們要求的約束條件一般而言相當直接又明確(?????)
加諸於template參數身上的隱式接口,就像加諸於class對象身上的的顯式接口一樣真實,而且兩者都在編譯期完成檢查。

請記住:
1.class和template都支持接口和多態
2.class而言接口是顯式的,以函數簽名位中心。多態則是通過virtual函數發生於運行期。
3.對template參數而言,接口是隱式,基於有效表達式。多態則是通過template具現化和函數重載解析發生於編譯期。



條款42:瞭解typename的雙重意義
當我們聲明template類型參數,class和typename的意義完全相同。然後C++並不是總把typename和class視爲等價。有時候你一定得使用typename。假設我們有個template function,接受一個STL兼容容器爲參數,容器內持有的對象被賦值爲int:
template<typename C>
void print2nd(const C& container){
	
	if(container.size() >= 2){
		
		C::const_iterator iter(container.begin());
		++iter;
		int value = *iter;
		std::cout << value;
	}
}


上述代碼中的兩個local變量iter和value。iter的類型是C::const_iterator,實際是什麼取決於template參數C。template內出現的名稱如果相依於某個template參數,稱之爲從屬名。如果叢書名稱在class內呈嵌套狀,稱之爲嵌套從屬名。C::const_iterator就是這樣一個名稱。而value,類型是int,不依賴於任何template參數的名稱,這樣的叫做非從屬名稱。
嵌套從屬名有可能導致解析困難,例如:
template<typename C>
void print2nd(const C& container){
	
	C::const_iterator* x;
}

看起來好像聲明x爲一個local變量,但它之所以被那麼認爲,只因爲我們已經知道C::const_iterator是個類型。如果C::const_iterator不是個類型呢?如果C有個static成員變量恰好被命名爲const_iterator,那樣上述代碼就不再是聲明爲一個local變量,而是一個相乘動作。現在看看print2nd起始處:
template<typename C>
void print2nd(const C& container){
	
	if(container.size() >= 2){
		
		C::const_iterator iter(container.begin());//這個名稱被假設爲非類型(??????)
         }
}

現在清楚爲什麼這不是有效的C++代碼了吧。iter聲明式只有在C::const_iterator是個類型時才合理,但我們並沒有告訴C++說它是,於是C++假設它不是。若要矯正這個形式,我們必須告訴C++說C::const_iterator是個類型。只要在它之前加上typename即可:

template<typename C>
void print2nd(const C& container){
	
	if(container.size() >= 2){
		
		typename C::const_iterator iter(container.begin());
		++iter;
		int value = *iter;
		std::cout << value;
	}
}

一般性規則很簡單:任何時候當你想要在template中指涉一個嵌套從屬類型名稱,就必須在緊鄰它的前一個位置放置上typename。
以下內容沒看懂,不抄了

typename必須作爲嵌套從屬類型名稱的前綴詞這一規則的例外是,typename不可以出現在base class list內的嵌套從屬類型名稱之前,也不可以在成員初始化列中作爲基類修飾符:

template<typename T>
class Derived: public Base<T>::Nested{ //base class list中不允許typename
public:
	explicit Derived(int x)
	: Base<T>::Nested(x){				//成員初始列中不允許tepename
		typename Base<T>::Nested temp;  //既不在base class list中也不在mem.init.list中
		//..
	}
	//..
}

請記住:
1.聲明template參數時,關鍵字class和typename可互換。
2.請使用關鍵字typename標識嵌套從屬類型名;但不得在base class list或者member initialization list內以它作爲base class修飾符。








































發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章