正文
1. class和typename含義相同的例子
問題:在下面的模板聲明中class和typename的區(qū)別是什么?
1 template<class T> class Widget; // uses “class”2 3 template<typename T> class Widget; // uses “typename”
答案:沒有任何區(qū)別。當聲明一個模板類型參數(shù)時,class和typename意味著相同的事情。一些程序員喜歡使用class,因為容易敲打。其他的(包括我)更加喜歡使用typename,因為用它表明參數(shù)不需要是一個class類型。一些程序員在允許使用任何type的時候使用typename,只用對用戶自定義的類型使用class。但是從C++ 的觀點來看,在聲明模板參數(shù)的時候class和typename意味著相同的事情。
2. 必須使用typename的例子
然而,C++并不總是將class和typename同等對待。有時你必須使用typename。為了理解在什么時候必須使用,我們必須討論能夠在模板中引用的兩種名字。
假設(shè)我們有一個函數(shù)模板,用和STL兼容的容器作為模板參數(shù),此容器中包含的對象能夠被賦值給int類型。進一步假設(shè)這個函數(shù)打印容器中的第二個元素值。我在下面以愚蠢的方式實現(xiàn)了一個愚蠢的函數(shù),它甚至不能通過編譯,但是請忽略這些事情,看下面的例子:
1 template<typename C> // print 2nd element in 2 void print2nd(const C& container) // container; 3 { // this is not valid C++! 4 if (container.size() >= 2) { 5 C::const_iterator iter(container.begin()); // get iterator to 1st element 6 ++iter; // move iter to 2nd element 7 int value = *iter; // copy that element to an int 8 9 std::cout << value; // print the int10 11 } 12 13 }
我對此函數(shù)中的兩個本地變量做了高亮,iter和value。Iter的類型是C::const_iterator,它依賴于模板參數(shù)C。模板中依賴于模板參數(shù)的名字被稱作依賴名字(dependent names)。當一個依賴名字嵌套在一個類中的時候,我把它叫做內(nèi)嵌依賴名字(nested dependent name)。C::const_iterator是一個內(nèi)嵌依賴名字。事實上,它是一個內(nèi)嵌依賴類型名字(nested dependent type name),也即是指向一個類型(type)的內(nèi)嵌依賴名字。
對于print2nd中的其他本地變量,value,類型為int。int不依賴于任何模板參數(shù)。這種名字被稱作“非依賴名字”(non-dependent names)。(我不知道為什么不把它們叫做獨立名字(independent names)?!皀on-dependent”是一種不好的命名方式,但畢竟它是術(shù)語,所以需要遵守這個約定。)
內(nèi)嵌依賴名字會導(dǎo)致解析困難。例如,如果我們讓print2nd函數(shù)以下面的方式開始,會更加愚蠢:
1 template<typename C> 2 3 void print2nd(const C& container) 4 5 { 6 7 C::const_iterator * x; 8 9 ...10 11 }
看上去像是我們聲明了一個本地變量x,這個x指針指向一個C::const_iterator。但是它看上去是這樣的僅僅因為我們“知道”C::const_iterator是一個type。但是如果C::const_iterator不是一個type會是怎樣呢?如果C有個靜態(tài)數(shù)據(jù)成員恰好被命名為const_iterator會發(fā)生什么?如果x恰巧是一個全局變量的名字呢?在這種情況下,上面的code就不會聲明一個本地變量,它會是C::const_iterator和x的乘積!聽起來有些瘋狂,但這是可能的,實現(xiàn)C++編譯器的人員也必須考慮到所有可能的輸入,包括一些看起來很瘋狂的例子。
直到C被確定之前,沒有辦法知道C::const_iterator是否是一個type,當函數(shù)模板print2nd被解析的時候,C不能夠被確認。為了處理這種模棱兩可的問題,C++有一個準則:如果解析器在模板中碰到了一個內(nèi)嵌依賴名字,它不會認為這是一個type,除非你告訴它。默認情況下,內(nèi)嵌依賴名字不是types。(對于這個規(guī)則有個例外,一會會提到。)
將上面的規(guī)則記在心中,再看一次print2nd的開始部分:
1 template<typename C>2 void print2nd(const C& container)3 {4 if (container.size() >= 2) {5 C::const_iterator iter(container.begin()); // this name is assumed to6 ... // not be a type
現(xiàn)在應(yīng)該清楚為什么這不是有效的C++了。Iter的聲明只有在C::const_iterator是一個type的情況下才有意義,但是我們并沒有告知C++它是一個類型,于是C++假設(shè)它不是一個類型。為了糾正這種情況,我們必須告訴C++ C::const_iterator是一個類型。我們將typename放在type之前就能達到這個目的:
1 template<typename C> // this is valid C++ 2 3 void print2nd(const C& container) 4 5 { 6 7 if (container.size() >= 2) { 8 9 typename C::const_iterator iter(container.begin()); 10 11 ... 12 13 } 14 15 }
這個規(guī)則很簡單:在一個模板中,任何時候你引用一個內(nèi)嵌依賴類型名字,你都必須在名字前加上typename。(也有例外,一會會提到。)
typename應(yīng)該只被用來確認一個內(nèi)嵌依賴類型名字;其他的名字不應(yīng)該加這個前綴。例如,下面的函數(shù)模板使用兩個參數(shù),一個容器和一個容器的迭代器:
1 template<typename C> // typename allowed (as is “class”)2 void f(const C& container, // typename not allowed3 typename C::iterator iter); // typename required
C不是內(nèi)嵌依賴類型名字(它沒有內(nèi)嵌在任何依賴于模板參數(shù)的東西中),所以在聲明容器的時候不應(yīng)該加typename,但是C::iterator是一個內(nèi)嵌依賴類型名字,所以需要加typename。
3. 一個例外——不能使用typename的地方
”typename”必須加在內(nèi)嵌依賴類型名字之前“這個規(guī)則有一個例外:基類列表中的內(nèi)嵌依賴類型名字或者成員初始化列表中的基類標識符不能加typename。例如:
1 template<typename T> 2 class Derived: public Base<T>::Nested { // base class list: typename not 3 4 public: // allowed 5 6 explicit Derived(int x) 7 8 9 10 : Base<T>::Nested(x) // base class identifier in mem.11 12 { // init. list: typename not allowed13 14 15 typename Base<T>::Nested temp; // use of nested dependent type16 ... // name not in a base class list or17 } // as a base class identifier in a18 ... // mem. init. list: typename19 required20 };
這種不一致性令人感到厭煩,但是一旦你有了一點經(jīng)驗,你就會注意到它。
4. 最后的例子——為typename使用typedef
讓我們看最后一個typename的例子,因為它代表了你將會在真實代碼中看到的某些東西。假設(shè)我們正在實現(xiàn)一個函數(shù)模板,帶了一個迭代器參數(shù),我們想為迭代器指向的對象做一份本地拷貝,temp。我們可以像下面這樣實現(xiàn):
1 template<typename IterT>2 void workWithIterator(IterT iter)3 {4 typename std::iterator_traits<IterT>::value_type temp(*iter);5 ...6 }
不要讓 std::iterator_traits<IterT>::value_type 嚇到你。這只是標準特性類(standard traits class)的一種使用方法,這是“類型IterT對象指向的類型“的C++實現(xiàn)方式。這個句子聲明了一個本地變量(temp),它的類型同IterT對象指向的對象的類型一致,它將temp初始化為iter指向的對象。如果IterT是vector<int>::iterator,那么temp就是int類型的。如果IterT是list<string>::iterator,temp就是string類型的。因為std::iterator_traits<IterT>::value_type是一個內(nèi)嵌依賴類型名字(在iterator_traits<IterT>內(nèi)部value_type是內(nèi)嵌的,IterT是一個模板參數(shù)),我們必須為其添加typename。
如果你認為讀std::iterator_traits<IterT>::value_type是一件不讓人愉快的事情,想像一下將其打出來會是什么樣的。如果你像大部分程序員一樣,多次輸入這個表達式的想法是可怕的,所以你會想為其創(chuàng)建一個typedef。對于像value_type這樣的特性(traits)成員名字來說(對于特性的信息看Item47),使用慣例是使得typedef名字和特性成員名字相同,所以這樣一個本地typedef通常被定義成下面這樣:
1 template<typename IterT>2 void workWithIterator(IterT iter)3 {4 typedef typename std::iterator_traits<IterT>::value_type value_type;5 value_type temp(*iter);6 ...7 }
許多程序員發(fā)現(xiàn)將“typedef typename“并列看上去不和諧,但是對于使用內(nèi)嵌依賴類型名字的規(guī)則來說,這是一個合乎邏輯的結(jié)果。你會很快習慣這種用法。畢竟,你有著很強的驅(qū)動力。你想輸入typename std::iterator_traits<IterT>::value_type多少次呢?
5. Typename的執(zhí)行因編譯器而異
作為結(jié)束語,我應(yīng)該提及的是關(guān)于typename規(guī)則的強制執(zhí)行隨著編譯器的不同而不同,一些編譯器接受需要typename但實際上沒有輸入的情況;一些編譯器接受輸入了typename但實際上不允許的情況;還有一些(通常是老的編譯器)在需要輸入typename時拒絕了typename輸入。這就意味著typename和內(nèi)嵌依賴類型名字的交互會產(chǎn)生讓你頭痛的問題。
6. 總結(jié)
當聲明模板參數(shù)的時候,class和typename是可以互換的。
使用typename來識別內(nèi)嵌依賴類型名字,但在基類列表中或者成員初始化列表中的基類標識符除外。
作者: HarlanC
博客地址: http://www.cnblogs.com/harlanc/
個人博客: http://www.harlancn.me/
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出, 原文鏈接 .
http://www.cnblogs.com/harlanc/p/6661228.html