程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 《Effective Modern C++》讀書筆記 Item 1 理解模板類型推導,effectivemodernc

《Effective Modern C++》讀書筆記 Item 1 理解模板類型推導,effectivemodernc

編輯:C++入門知識

《Effective Modern C++》讀書筆記 Item 1 理解模板類型推導,effectivemodernc


最近發現了《Effective Modern C++》這本書,作者正是大名鼎鼎的Scott Meyers——《Effective C++》、《Effective STL》的作者。

而就在C++11逐漸普及,甚至是C++14的新特性也進入大家的視野的時候,《Effective Modern C++》一書應運而生。此書與其前輩一樣,通過數十個條款來展開,只不過這次是集中於C++11和C++14的新特性。autodecltype、move、lambda表達式……這些強而有力的新特性背後到底隱藏著哪些細節和要點?……

閱讀這本書的時候,感受到的豁然開朗的愉悅與初學C++時看Scott前幾本著作時別無二致。遂嘗試摘錄一二,結合所想,做些記錄,同時也試著檢查一些自己的知識點有哪些欠缺,希望大家能多多指正。

 

注意:

蛤蛤蛤蛤蛤

      這樣的方框裡的片段完全不來自於原書,而是我自己的理解。

 

Item 1 Understand template type deduction - 理解模板類型推導

模板類型推導是C++長期以來的特性。比如:

template<typename T>
void f(ParamType param);

f(expr);  // 調用 f

其中 ParamType 可以是和 有關的類型,只不過包含一些修飾,比如 const 或引用修飾符(reference qualifier)。如:

template<typename T>
void f(const T& param); // ParamType  const T&

對於這樣的調用:

int x = 0;
f(x);

一個特化(Specialize)的函數就經由類型推導生成了,T 被推導(deduce)為 intParamType 則被推導為 const int&

上面這種過程是類型推導,而

template<> void f<int>(int);

就不算類型推導了——因為並沒有進行“類型推導”,而是直接指定了——cppreference上將這叫做instantiate,實例化。編譯器將特化有特定模板參數的函數模板。

 

在這種形式中,T 的推導不僅依賴於 expr 的類型,還和 ParamType 的形式(form)有關。對此書中給出三種情形:

 

情形1:ParamType 是指針(pointer)或引用(reference)類型,但不是universal引用(universal reference)

(此時書中並未詳述什麼叫universal引用,不過對此情形影響不大,因為universal引用首先就不是左值引用,即不是形如 int&、T&

在第一種情形下,類型推導有如下規則:

上兩條中,expr 指的就是函數的實參(argument),而 ParamType 是形參(parameter)的類型。書中例子為:

template<typename T>
void f(T& param);

int x = 27;
const int cx = x;
const int& rx = x;

f(x);   // T -> int,       ParamType -> int&
f(cx);  // T -> const int, ParamType -> const int&
f(rx);  // T -> const int, ParamType -> const int&

expr,即上例的 x、cx、rx,去掉引用部分後為 int,const int,而 param 將要對這幾種類型的變量建立引用,ParamType 就推導出了上述的結果。

其中很重要的一點:

當傳遞一個 const 對象到一個引用參數(parameter)時,調用者希望這個對象能保持 const 特性,即不變性。

模板類型推導遵從這一要點。故傳遞 const 對象到模板參數 T& 是安全的,不會丟失 const 屬性。

並且以上規則對於右值引用也是成立的。

而將上例中的 ParamType 改為 const T& 時,上例三次調用全部將 ParamType 推導為 const int&T 則每次都為 int。因為 ParamType 的形式中帶有了 const,匹配後 T 就不需要帶有 const 了。

 

而對於 ParamType 是指針的情形,推導過程也是同樣的。只是去除了“忽略引用部分”這一步,只是對指針類型進行模式匹配。

 

情形2:ParamType 是universal引用

模板函數的參數是universal引用的時候,比如“像是”右值(rvalue)引用,即 T&& 這樣的類型,其中 T 的模板類型參數。

我想,所謂universal引用,可以先參考“引用疊加效果”表:

&  & -> &
&  && -> &
&&  & -> &
&&  && -> &&

我想這可能念做:&引用的&引用是&引用,&引用的&&引用是&引用,&&引用的&引用是&引用……

我的理解是,其中任一種引用的&&引用都是原型,所以叫做universal引用吧。由於是疊加在未確定的模板類型 T 上的,所以寫法雖然一樣,但並不是右值引用,因為右值引用是作用於明確類型上的。

參考資料 http://stackoverflow.com/questions/20364297/why-universal-references-have-the-same-syntax-as-rvalue-references

對於universal引用,類型推導規則為:

規則1可以對照引用疊加表,expr 的類型就是 -> 號右邊的,如果它是左值即&,能通過universal引用變成這樣狀態的也只有左值&。即使 ParamType 被聲明為和右值引用類似的形式,ParamType 本身也被推導為左值引用。

我認為這一推導正是由於待定的 ParamType 並不能表示一個右值引用類型,而只能作為一種“帶有未知量 T”的類型運算表達式。比如 若是 int&&,則 T& 就是 int&

書中對於情形2的例子為:

template<typename T>
void f(T&& param); // param is now a universal reference
int x = 27; const int cx = x; const int& rx = x;
f(x); // x -> lvalue, T -> int&, ParamType -> int& f(cx); // cx -> lvalue, T -> const int&, ParamType -> const int& f(rx); // rx -> lvalue, T -> const int&, ParamType -> const int& f(27); // 27 -> rvalue, T -> int, ParamType -> int&&

 第4個調用即為退回情形1規則的情況。expr 是一個右值,則進行模式匹配後被綁定到 int&&,其中 Tint

 

情形3:ParamType 既非指針,也非引用

就像這樣,按值傳遞/按拷貝傳遞(pass-by-value):

template<typename T>
void f(T param);

那麼 param 總是對實參(argument)進行拷貝。此情形有規則:

int x = 27; 
const int cx = x; 
const int& rx = x; 
f(x); // T and ParamTypes -> int f(cx); // T and ParamTypes -> int f(rx); // T and ParamTypes -> int

因為 param 總是 expr 的拷貝,所以無論怎樣都不會影響 expr,所以 expr constvolatile 這些特性,都和 param 無關了。

這也正符合上面所說的,調用者希望傳入的對象原本具有的特性(如 const)不受影響,程序的實現要遵從這一希望。

原書在這裡舉了一個 const char * const 按拷貝傳遞(按值傳遞——相對於按引用傳遞)的例子,不細表。

 

數組作為實參

C/C++都有這樣一個特性,那就是數組的退化(decay):

const char str[] = "hello";  // const char[6]
const char *p = str;         // 數組退化為指針

很明顯 str p 的類型是不同的。而且對於C中的語法,是可以將函數的參數聲明為數組的形式的,但是以下兩者卻是相同的:

void func(char str[]);
void func(char *str);

這是因為數組形式的形參(parameter),會被當作指針形式的形參處理。

因此對於按值傳遞的模板參數 T 來說,實參為數組 char[] 時,T 被推導為 char *。(可以認為數組的退化先發生。)

 

但模板參數為引用的時候,是能“真正”引用到傳入的數組的(即不發生數組退化):

template<typename T>
void f(T& param);

f(str);   // T -> const char[6], ParamType -> const char (&)[6]

一個例子,通過模板在編譯期獲取數組大小(代碼中暫時無關的部分被去掉了):

template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) 
{ 
    return N;
}

 

函數作為實參

除了數組之外,函數也會退回為指針。但同時,同樣能通過模板提供引用類型參數來避免退化:

void someFunc(int, double); // someFunc -> void(int, double)
template<typename T> void f1(T param);

template<typename T> void f2(T& param);
f1(someFunc); // ParamType -> void (*)(int, double) f2(someFunc); // ParamType -> void (&)(int, double)

數組和函數的退化都是針對其標識符。

 

此條款的注意點

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved