讀書筆記 effective c++ Item 42 理解typename的兩種意義

来源:http://www.cnblogs.com/harlanc/archive/2017/04/03/6661228.html
-Advertisement-
Play Games

1. class和typename意義相同的例子 問題:在下麵的模板聲明中class和typename的區別是什麼? 答案:沒有任何區別。當聲明一個模板類型參數時,class和typename意味著相同的事情。一些程式員喜歡使用class,因為容易敲打。其他的(包括我)更加喜歡使用typename, ...


1. class和typename含義相同的例子

問題:在下麵的模板聲明中class和typename的區別是什麼?

1 template<class T> class Widget;     // uses “class”
2 
3 template<typename T> class Widget;            // uses “typename”

 

答案:沒有任何區別。當聲明一個模板類型參數時,class和typename意味著相同的事情。一些程式員喜歡使用class,因為容易敲打。其他的(包括我)更加喜歡使用typename,因為用它表明參數不需要是一個class類型。一些程式員在允許使用任何type的時候使用typename,只用對用戶自定義的類型使用class。但是從C++ 的觀點來看,在聲明模板參數的時候class和typename意味著相同的事情。

2. 必須使用typename的例子

然而,C++並不總是將class和typename同等對待。有時你必須使用typename。為了理解在什麼時候必須使用,我們必須討論能夠在模板中引用的兩種名字。

假設我們有一個函數模板,用和STL相容的容器作為模板參數,此容器中包含的對象能夠被賦值給int類型。進一步假設這個函數列印容器中的第二個元素值。我在下麵以愚蠢的方式實現了一個愚蠢的函數,它甚至不能通過編譯,但是請忽略這些事情,看下麵的例子: 

 

 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 int
10 
11 }                                                 
12 
13 }     

                                    

 我對此函數中的兩個本地變數做了高亮,iter和value。Iter的類型是C::const_iterator,它依賴於模板參數C。模板中依賴於模板參數的名字被稱作依賴名字(dependent names)。當一個依賴名字嵌套在一個類中的時候,我把它叫做內嵌依賴名字(nested dependent name)。C::const_iterator是一個內嵌依賴名字。事實上,它是一個內嵌依賴類型名字(nested dependent type name),也即是指向一個類型(type)的內嵌依賴名字。

對於print2nd中的其他本地變數,value,類型為int。int不依賴於任何模板參數。這種名字被稱作“非依賴名字”(non-dependent names)。(我不知道為什麼不把它們叫做獨立名字(independent names)。“non-dependent”是一種不好的命名方式,但畢竟它是術語,所以需要遵守這個約定。)

內嵌依賴名字會導致解析困難。例如,如果我們讓print2nd函數以下麵的方式開始,會更加愚蠢:

 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有個靜態數據成員恰好被命名為const_iterator會發生什麼?如果x恰巧是一個全局變數的名字呢?在這種情況下,上面的code就不會聲明一個本地變數,它會是C::const_iterator和x的乘積!聽起來有些瘋狂,但這是可能的,實現C++編譯器的人員也必須考慮到所有可能的輸入,包括一些看起來很瘋狂的例子。

 

直到C被確定之前,沒有辦法知道C::const_iterator是否是一個type,當函數模板print2nd被解析的時候,C不能夠被確認。為了處理這種模棱兩可的問題,C++有一個準則:如果解析器在模板中碰到了一個內嵌依賴名字,它不會認為這是一個type,除非你告訴它。預設情況下,內嵌依賴名字不是types。(對於這個規則有個例外,一會會提到。)

 

將上面的規則記在心中,再看一次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 to
6 ... // not be a type

 

現在應該清楚為什麼這不是有效的C++了。Iter的聲明只有在C::const_iterator是一個type的情況下才有意義,但是我們並沒有告知C++它是一個類型,於是C++假設它不是一個類型。為了糾正這種情況,我們必須告訴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 }

 

這個規則很簡單:在一個模板中,任何時候你引用一個內嵌依賴類型名字,你都必須在名字前加上typename。(也有例外,一會會提到。)

 

typename應該只被用來確認一個內嵌依賴類型名字;其他的名字不應該加這個首碼。例如,下麵的函數模板使用兩個參數,一個容器和一個容器的迭代器:

                                                                                                  

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

 

C不是內嵌依賴類型名字(它沒有內嵌在任何依賴於模板參數的東西中),所以在聲明容器的時候不應該加typename,但是C::iterator是一個內嵌依賴類型名字,所以需要加typename。

 

3. 一個例外——不能使用typename的地方

 

”typename”必須加在內嵌依賴類型名字之前“這個規則有一個例外:基類列表中的內嵌依賴類型名字或者成員初始化列表中的基類標識符不能加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 allowed
13 
14 
15 typename Base<T>::Nested temp; // use of nested dependent type
16 ... // name not in a base class list or
17 } // as a base class identifier in a
18 ... // mem. init. list: typename
19 required
20 };

 

這種不一致性令人感到厭煩,但是一旦你有了一點經驗,你就會註意到它。

 

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

 

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

 

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++實現方式。這個句子聲明瞭一個本地變數(temp),它的類型同IterT對象指向的對象的類型一致,它將temp初始化為iter指向的對象。如果IterT是vector<int>::iterator,那麼temp就是int類型的。如果IterT是list<string>::iterator,temp就是string類型的。因為std::iterator_traits<IterT>::value_type是一個內嵌依賴類型名字(在iterator_traits<IterT>內部value_type是內嵌的,IterT是一個模板參數),我們必須為其添加typename。

如果你認為讀std::iterator_traits<IterT>::value_type是一件不讓人愉快的事情,想像一下將其打出來會是什麼樣的。如果你像大部分程式員一樣,多次輸入這個表達式的想法是可怕的,所以你會想為其創建一個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 }

 

許多程式員發現將“typedef typename“併列看上去不和諧,但是對於使用內嵌依賴類型名字的規則來說,這是一個合乎邏輯的結果。你會很快習慣這種用法。畢竟,你有著很強的驅動力。你想輸入typename std::iterator_traits<IterT>::value_type多少次呢?

5. Typename的執行因編譯器而異

作為結束語,我應該提及的是關於typename規則的強制執行隨著編譯器的不同而不同,一些編譯器接受需要typename但實際上沒有輸入的情況;一些編譯器接受輸入了typename但實際上不允許的情況;還有一些(通常是老的編譯器)在需要輸入typename時拒絕了typename輸入。這就意味著typename和內嵌依賴類型名字的交互會產生讓你頭痛的問題。

6. 總結

  • 當聲明模板參數的時候,class和typename是可以互換的。
  • 使用typename來識別內嵌依賴類型名字,但在基類列表中或者成員初始化列表中的基類標識符除外。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • public class SqlHelper { public static string connstr= ConfigurationManager.ConnectionStrings["Connstr"].ConnectionString; /// <summary> /// 執行增刪改 /// ...
  • 二進位日誌簡單介紹 MySQL的二進位日誌(binary log)是一個二進位文件,主要用於記錄修改數據或有可能引起數據變更的MySQL語句。二進位日誌(binary log)中記錄了對MySQL資料庫執行更改的所有操作,並且記錄了語句發生時間、執行時長、操作數據等其它額外信息,但是它不記錄SELE... ...
  • 一.索引的作用 一般的應用系統,讀寫比例在10:1左右,而且插入操作和一般的更新操作很少出現性能問題,遇到最多的,也是最容易出問題的,還是一些複雜的查詢操作,所以查詢語句的優化顯然是重中之重。 在數據量和訪問量不大的情況下,mysql訪問是非常快的,是否加索引對訪問影響不大。但是當數據量和訪問量劇增 ...
  • 本文來源於轉載:http://www.cnblogs.com/zhangq723/archive/2012/03/13/2394102.html 前提:在使用下麵的備份方式之前需要確保你的Sqlserver Agent服務啟動,切設置為自動啟動。否則當你伺服器重啟了但是Agent服務沒有啟動,那麼自 ...
  • 好久沒更新博客了,上周在x64位的操作系統中安裝好了32位或64位的oracle 11g客戶端,但用SSIS或Microsoft SQL Server 2012報表生成器3.0去連接oracle 11g死活都連接不上,報各類錯誤,百度了網上給出的解決方案也沒解決,這個問題困擾了兩天,但用同事的電腦去 ...
  • 本文介紹binlog的作用以及幾個重要參數的使用方法,同時通過實驗來描述binlog內部記錄內容:row 、statement跟mixed的設置下,記錄了哪些東西,最後會簡單介紹下binlog server的搭建以及一些關於binlog使用的小Tips。 理解跟熟悉binlog相關內容,對複製原理及 ...
  • 讀書筆記,待補充完善 MySQL緩存分類 InnoDB緩衝池 InnoDB日誌文件和MyIsAM數據的操作系統緩存 MyIsAM鍵緩存 查詢緩存 無法手工配置的緩存,二進位日誌,表定義文件的操作系統緩存 其它緩存,通常不需要太多記憶體 InnoDB緩衝池 作用: 1.緩存的對象包括:數據行,索引,插入 ...
  • Converter只完成了數據類型的轉換,卻不負責輸入輸出數據的格式化工作,日期時間、貨幣等雖都以字元串形式存在,卻有不同的格式。 Spring格式化框架要解決的問題是:從格式化的數據中獲取真正的數據,綁定數據,將處理完成的數據輸出為格式化的數據。Formatter介面就是該框架最重要的介面 Con ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...