1. 程式人生 > >C++中typename和class在宣告模板時的區別

C++中typename和class在宣告模板時的區別

問題

在下面的 template declarations(模板宣告)中 class 和 typename 有什麼不同?

template<class T> class Widget; // uses "class"
template<typename T> class Widget; // uses "typename"

答案:沒什麼不同。

在宣告一個 template type parameter(模板型別引數)的時候,class 和 typename 意味著完全相同的東西。

一些程式設計師更喜歡在所有的時間都用 class,因為它更容易輸入。其他人(包括我本人)更喜歡 typename,因為它暗示著這個引數不必要是一個 class type(類型別)。少數開發者在任何型別都被允許的時候使用 typename,而把 class 保留給僅接受 user-defined types(使用者定義型別)的場合。

但是從 C++ 的觀點看,class 和 typename 在宣告一個 template parameter(模板引數)時意味著完全相同的東西。

然而,C++ 並不總是把 class 和 typename 視為等同的東西

原因解析

相信學習C++的人對class這個關鍵字都非常明白,class用於定義類,在模板引入c++後,最初定義模板的方法為:

template<class T>

在這裡class關鍵字表明T是一個型別,後來為了避免class在這兩個地方的使用可能給人帶來混淆,所以引入了typename這個關鍵字,它的作用同
class一樣表明後面的符號為一個型別

這樣在定義模板的時候就可以使用下面的方式了

template<typename T>......

在模板定義語法中關鍵字class與typename的作用完全一樣。

但是,typename難道僅僅在模板定義中起作用嗎?

其實不是這樣,typename另外一個作用為:使用巢狀依賴型別(nested depended name)

typename的用處

按 C++ 標準來說,template 用於基礎資料型別,typename 指型別名,T 可以取 char int double 等。

template 用於類,T 可以取任何類。

但是這裡有一個問題,結構體應該用 typename 還是 class? 結構體肯定不是基礎資料型別,但也不是類。

所以實際情況是,template 的 T 也可以取基礎資料型別,tempate 的 T 也可以取類。
因此,從 C++ 的觀點看,class 和 typename 在宣告一個 template parameter(模板引數)時意味著完全相同的東西。

然而,C++ 並不總是把 class 和 typename 視為等同的東西

但有一個特例,就是當 T 是一個類,而這個類又有子類(假設名為 innerClass) 時,應該用

template<typename>:

typename T::innerClass   myInnerObject;

這裡的 typename 告訴編譯器,T::innerClass 是一個類,程式要宣告一個 T::innerClass 類的物件,而不是宣告 T 的靜態成員,而 typename 如果換成 class 則語法錯誤。

示例-基類物件的巢狀依賴

我們在模版中使用域操作符引用了基類的物件

#include <iostream>
#include <string>


class Base
{

};


class Derived  : Base
{

};

template<class T>
void MyMethod( T derived)
{
    typename T::Base base1;    // success
    T::Base base2;    // error -=> // need 'typename' before 'T:: Base' because 'T' is a dependent scope|
}



int main(void)
{

}

示例–內部成員的巢狀依賴

#include <iostream>
#include <string>



class MyArray
{
public:
    typedef int LengthType;
};

template<class T>
void MyMethod( T myarr )
{
    typedef typename T::LengthType LengthType;    // success
    typedef  T::LengthType LengthType;              //  error need 'typename' before 'T::LengthType' because 'T' is a dependent scope|

    LengthType length = myarr.GetLength;
}



int main(void)
{

}

巢狀依賴型別高階進階

假設我們有一個函式的模板,它能取得一個STL-compatible container(STL 相容容器)中持有的能賦值給 ints 的物件。進一步假設這個函式只是簡單地列印它的第二個元素的值。它是一個用糊塗的方法實現的糊塗的函式,而且就像我下面寫的,它甚至不能編譯,但是請將這些事先放在一邊——有一種方法能發現我的愚蠢:

template<typename C> // print 2nd element in
void print2nd(const C& container) // container;
{
 // this is not valid C++!
 if (container.size() >= 2) {
  C::const_iterator iter(container.begin()); // get iterator to 1st element
  ++iter; // move iter to 2nd element
  int value = *iter; // copy that element to an int
  std::cout << value; // print the int
 }
}

我突出了這個函式中的兩個 local variables(區域性變數),iter 和 value。

iter 的型別是 C::const_iterator,一個依賴於 template parameter(模板引數)C 的型別。一個 template(模板)中的依賴於一個 template parameter(模板引數)的名字被稱為 dependent names(依賴名字)。當一個 dependent names(依賴名字)巢狀在一個 class(類)的內部時,我稱它為* nested dependent name(巢狀依賴名字)*。C::const_iterator 是一個 nested dependent name(巢狀依賴名字)。實際上,它是一個 nested dependent type name(巢狀依賴型別名)也就是說,一個涉及到一個 type(型別)的 nested dependent name(巢狀依賴名字)。

print2nd 中的另一個 local variable(區域性變數)value 具有 int 型別。int 是一個不依賴於任何 template parameter(模板引數)的名字。這樣的名字以 non-dependent names(非依賴名字)聞名。(我想不通為什麼他們不稱它為 independent names(無依賴名字)。如果,像我一樣,你發現術語 “non-dependent” 是一個令人厭惡的東西,你就和我產生了共鳴,但是 “non-dependent” 就是這類名字的術語,所以,像我一樣,轉轉眼睛放棄你的自我主張。)

ested dependent name(巢狀依賴名字)會導致解析困難。例如,假設我們更加愚蠢地以這種方法開始 print2nd:

template<typename C>
void print2nd(const C& container)
{
 C::const_iterator * x;
 ...
}

這看上去好像是我們將 x 宣告為一個指向 C::const_iterator 的 local variable(區域性變數)。但是它看上去如此僅僅是因為我們知道 C::const_iterator 是一個 type(型別)。但是如果 C::const_iterator 不是一個 type(型別)呢?如果 C 有一個 static data member(靜態資料成員)碰巧就叫做 const_iterator 呢?再如果 x 碰巧是一個 global variable(全域性變數)的名字呢?在這種情況下,上面的程式碼就不是宣告一個 local variable(區域性變數),而是成為 C::const_iterator 乘以 x!當然,這聽起來有些愚蠢,但它是可能的,而編寫 C++ 解析器的人必須考慮所有可能的輸入,甚至是愚蠢的。

直到 C 成為已知之前,沒有任何辦法知道 C::const_iterator 到底是不是一個 type(型別),而當 template(模板)print2nd 被解析的時候,C 還不是已知的。C++ 有一條規則解決這個歧義:如果解析器在一個 template(模板)中遇到一個 nested dependent name(巢狀依賴名字),它假定那個名字不是一個 type(型別),除非你用其它方式告訴它。預設情況下,nested dependent name(巢狀依賴名字)不是 types(型別)。(對於這條規則有一個例外,我待會兒告訴你。)

記住這個,再看看 print2nd 的開頭:

template<typename C>
void print2nd(const C& container)
{
 if (container.size() >= 2) {
  C::const_iterator iter(container.begin()); // this name is assumed to

這為什麼不是合法的 C++ 現在應該很清楚了。iter 的 declaration(宣告)僅僅在 C::const_iterator 是一個 type(型別)時才有意義,但是我們沒有告訴 C++ 它是,而 C++ 就假定它不是。要想轉變這個形勢,我們必須告訴 C++ C::const_iterator 是一個 type(型別)。我們將 typename 放在緊挨著它的前面來做到這一點:

template<typename C> // this is valid C++
void print2nd(const C& container)
{
    if (container.size() >= 2)
     {
        typename C::const_iterator iter(container.begin());
        //...
    }
}

通用的規則很簡單:在你涉及到一個在 template(模板)中的 nested dependent type name(巢狀依賴型別名)的任何時候,你必須把單詞 typename 放在緊挨著它的前面。(重申一下,我待會兒要描述一個例外。)

typename 應該僅僅被用於標識 nested dependent type name(巢狀依賴型別名);其它名字不應該用它。例如,這是一個取得一個 container(容器)和這個 container(容器)中的一個 iterator(迭代器)的 function template(函式模板):

template<typename C> // typename allowed (as is "class")
void f(const C& container, // typename not allowed
typename C::iterator iter); // typename required

C 不是一個 nested dependent type name(巢狀依賴型別名)(它不是巢狀在依賴於一個 template parameter(模板引數)的什麼東西內部的),所以在宣告 container 時它不必被 typename 前置,但是 C::iterator 是一個 nested dependent type name(巢狀依賴型別名),所以它必需被 typename 前置。

“typename must precede nested dependent type names”(“typename 必須前置於巢狀依賴型別名”)規則的例外是 typename 不必前置於在一個 list of base classes(基類列表)中的或者在一個 member initialization list(成員初始化列表)中作為一個 base classes identifier(基類識別符號)的 nested dependent type name(巢狀依賴型別名)。例如:

template<typename T>
class Derived: public Base<T>::Nested {
 // base class list: typename not
 public: // allowed
  explicit Derived(int x)
  : Base<T>::Nested(x) // base class identifier in mem
  {
   // init. list: typename not allowed
 
   typename Base<T>::Nested temp; // use of nested dependent type
   //... // name not in a base class list or
  } // as a base class identifier in a
  //... // mem. init. list: typename required
};

這樣的矛盾很令人討厭,但是一旦你在經歷中獲得一點經驗,你幾乎不會在意它。

  讓我們來看最後一個 typename 的例子,因為它在你看到的真實程式碼中具有代表性。假設我們在寫一個取得一個 iterator(迭代器)的 function template(函式模板),而且我們要做一個 iterator(迭代器)指向的 object(物件)的區域性拷貝 temp,我們可以這樣做:

template<typename IterT>
void workWithIterator(IterT iter)
{
 typename std::iterator_traits<IterT>::value_type temp(*iter);
 // ...
}

不要讓 std::iterator_traits::value_type 嚇倒你。那僅僅是一個 standard traits class(標準特性類)的使用,用 C++ 的說法就是 “the type of thing pointed to by objects of type IterT”(“被型別為 IterT 的物件所指向的東西的型別”)。這個語句聲明瞭一個與 IterT objects 所指向的東西型別相同的 local variable(區域性變數)(temp),而且用 iter 所指向的 object(物件)對 temp 進行了初始化。如果 IterT 是 vector::iterator,temp 就是 int 型別。如果 IterT 是 list::iterator,temp 就是 string 型別。因為 std::iterator_traits::value_type 是一個 nested dependent type name(巢狀依賴型別名)(value_type 巢狀在 iterator_traits 內部,而且 IterT 是一個 template parameter(模板引數)),我們必須讓它被 typename 前置。

如果你覺得讀 std::iterator_traits::value_type 令人討厭,就想象那個與它相同的東西來代表它。如果你像大多數程式設計師,對多次輸入它感到恐懼,那麼你就需要建立一個 typedef。對於像 value_type 這樣的 traits member names(特性成員名),一個通用的慣例是 typedef name 與 traits member name 相同,所以這樣的一個 local typedef 通常定義成這樣:

template<typename IterT>
void workWithIterator(IterT iter)
{
 typedef typename std::iterator_traits<IterT>::value_type value_type;
 value_type temp(*iter);
 // ...
}

總結

很多程式設計師最初發現 “typedef typename” 並列不太和諧,但它是涉及 nested dependent type names(巢狀依賴型別名)規則的一個合理的附帶結果。你會相當快地習慣它。你畢竟有著強大的動機。你輸入 typename std::iterator_traits::value_type 需要多少時間?

編譯器與編譯器之間對圍繞 typename 的規則的執行情況的不同。一些編譯器接受必需 typename 時它卻缺失的程式碼;一些編譯器接受不許 typename 時它卻存在的程式碼;還有少數的(通常是老舊的)會拒絕 typename 出現在它必需出現的地方。這就意味著 typename 和 nested dependent type names(巢狀依賴型別名)的互動作用會導致一些輕微的可移植性問題。

  • 在宣告 template parameters(模板引數)時,class 和 typename 是可互換的。

  • 用 typename 去標識 nested dependent type names(巢狀依賴型別名),在 base class lists(基類列表)中或在一個 member initialization list(成員初始化列表)中作為一個 base class identifier(基類識別符號)時除外。

相關推薦

C++typenameclass宣告模板區別

問題 在下面的 template declarations(模板宣告)中 class 和 typename 有什麼不同? template<class T> class Widget; // uses "class" template

C++typenameclass區別

type .get true 能夠 class .... ray pla 依賴 在c++Template中很多地方都用到了typename與class這兩個關鍵字,而且好像可以替換,是不是這兩個關鍵字完全一樣呢? 相信學習C++的人對class這個關鍵字都非常明白,clas

C++structclass定義類的區別

C++中的struct對C中的struct進行了擴充,它已經不再只是一個包含不同資料型別的資料結構了,它已經獲取了太多的功能。struct能包含成員函式嗎? 能! struct能繼承嗎? 能!! struct能實現多型嗎? 能!!!  既然這些它都能實現,那它和clas

C# Struct Class區別總結

> 翻譯自 Manju lata Yadav 2019年6月2日 的博文 [《Difference Between Struct And Class In C#》](https://www.c-sharpcorner.com/blogs/difference-between-struct-and-cla

C#知識點總結系列:3、C#DelegateEvent以及它們的區別

的區別 sent () exit 功能 final 通知 bsp t對象 1.Monitor.Enter(object)方法是獲取鎖,Monitor.Exit(object)方法是釋放鎖,這就是Monitor最常用的兩個方法,當然在使用過程中為了避免獲取鎖之後因為異常,致鎖

C++mapset的使用與區別

set set是一種關聯式容器,其特性如下: set以RBTree作為底層容器 所得元素的只有key沒有value,value就是key 不允許出現鍵值重複 所有的元素都會被自動排序 不能通過迭代器來改變set的值,因為set的值就是鍵 針對這五點來說,

C++iostreamiostream.h有什麼區別

#include <iostream.h>是非標準輸入輸出流, #include <iostream>是標準輸入輸出流 C++中為了避免名字的衝突,特別引入了“名字空間的定義”,即namespace 當代碼中用<iostream.h>時,

c++模板typename class

在c++Template中很多地方都用到了typename與class這兩個關鍵字,而且在泛型程式設計的時候可以替換,但是 typename 和 class 並不是完全一致的。 相信學習C++的人對class這個關鍵字都非常明白,class用於定義類,在模板引入c++後,最初定義模板的方法

C++模板C++宣告模板能否用struct關鍵字代替class或者typename

        我們知道C++中宣告一個函式或者類的模板支援兩種關鍵字class和typename: template <class T> struct Person { public: T age; }; 或者 template <typenam

TypenameClass宣告模板區別

宣告template引數時,字首關鍵詞class和typename可互換。也就是說以下兩個沒有區別: (1)template<class T>class Widget; (2)template<typename>class Widget。 然而C++

定義模板typenameclass區別

在c++Template中很多地方都用到了typename與class這兩個關鍵字,而且好像可以替換,是不是這兩個關鍵字完全一樣呢? 相信學習C++的人對class這個關鍵字都非常明白,class用於定義類,在模板引入c++後,最初定義模板的方法為: temp

C++的全域性變數宣告定義

1.全域性變數 全域性變數在整個原始檔的作用域都是有效的,只需要在一個原始檔中定義全域性變數,在其他不包含全域性變數定義的原始檔中用extern關鍵字再次宣告這個全域性變數即可。 也可以在一個原始檔中定義這個全域性變數,在標頭檔案中用extern關鍵字再次宣告這個全域性變數,如果其它原始檔要

c++利用巨集來宣告定義變數

假設我們要定義一個配置類,類中包含了很多的配置成員,有一種通過巨集的方法可以讓我們方便的維護繁多的成員 在一個類中,定義一個變數需要型別,建構函式中給出的初始值。我們需要能夠像指令碼語言一樣進行配置變數: tconfig.h OPTION(m_data,OPT_INT,-1) OPTION(m_value

C++mapvector作形參如何給定預設引數?

      之前遇到過這種特殊場景, 我用static變數比較噁心地解決了問題, 其實, 有更優雅的方式:#include <iostream> #include <vector>

C++typename關鍵字的使用方法注意事項

目錄起因近日,看到這樣一行程式碼:typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor; 雖說已經有多年C++經驗,但上面這短短一行程式碼卻看得我頭皮發麻。看起來它

C#&&&,|||區別

.com blank img cnblogs png ref bsp 筆記 區別 當兩者都為邏輯運算符時。 其實沒什麽差別。 &&和||當已經確定結果時,不會對第二個操作數求值。也不知道什麽情況會用到這個差別。做個筆記好了。 http://blog.cs

C#Struct與Class區別

而是 適用於 ack 定義 cts 多態 支持 關鍵字 for class和struct最本質的區別是class是引用類型,而struct是值類型,它們在內存中的分配情況有所區別。 什麽是class? class(類)是面向對象編程的基本概念,是一種自定義數據結構類型,通

C++數字字符串的轉換

oat 不能 ring1 相關 輸出 displays tof spl 進制轉換 1、字符串數字之間的轉換(1)string --> char * string str("OK"); char * p = str.c_str();(2)char * -->

C++static_castdynamic_cast強制類型轉換

tro 父類 虛函數表 找到 virt 內部 pub 判斷 () 在C++標準中,提供了關於類型層次轉換中的兩個關鍵字static_cast和dynamic_cast。 一、static_cast關鍵字(編譯時類型檢查) 用法:static_cast < type-i

C++的###運算符

合並操作 col 否則 未定義 info merge eight 標識符 轉換 #和##運算符 #:構串操作符 構串操作符#只能修飾帶參數的宏的形參,它將實參的字符序列(而不是實參代表的值)轉換成字符串常量 #define STRING(x) #x#x#x #defin