正文

回到頂部

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ù),它甚至不能通過編譯,但是請忽略這些事情,看下面的例子: 

 

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

 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 }

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

                                    

 我對此函數(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ù)以下面的方式開始,會更加愚蠢:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

 1 template<typename C> 2  3 void print2nd(const C& container) 4  5 { 6  7 C::const_iterator * x; 8  9 ...10 11 }

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

 

看上去像是我們聲明了一個本地變量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的開始部分:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

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

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

 

現(xiàn)在應(yīng)該清楚為什么這不是有效的C++了。Iter的聲明只有在C::const_iterator是一個type的情況下才有意義,但是我們并沒有告知C++它是一個類型,于是C++假設(shè)它不是一個類型。為了糾正這種情況,我們必須告訴C++ C::const_iterator是一個類型。我們將typename放在type之前就能達到這個目的:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

 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 }

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

 

這個規(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。例如:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

 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 };

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

 

這種不一致性令人感到厭煩,但是一旦你有了一點經(jīng)驗,你就會注意到它。

 

回到頂部

4. 最后的例子——為typename使用typedef

 

讓我們看最后一個typename的例子,因為它代表了你將會在真實代碼中看到的某些東西。假設(shè)我們正在實現(xiàn)一個函數(shù)模板,帶了一個迭代器參數(shù),我們想為迭代器指向的對象做一份本地拷貝,temp。我們可以像下面這樣實現(xiàn):

 

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

1 template<typename IterT>2 void workWithIterator(IterT iter)3 {4 typename std::iterator_traits<IterT>::value_type temp(*iter);5 ...6 }

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

 

不要讓 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通常被定義成下面這樣:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

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 }

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

 

許多程序員發(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