<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                條款1:理解模板類型推導 ==================== ##Understand template type deduction. 當一個復雜系統的用戶忽略這個系統是如何工作的,那就再好不過了,因為“如何”扯了一堆系統的設計細節。從這個方面來度量,C++的模板類型推導是個巨大的成功。成百上萬的程序猿給模板函數傳遞完全類型匹配的參數,盡管有很多的程序猿會更加苛刻的給于這個函數推導的類型的嚴格描述。 如果上面的描述包括你,那我有好消息也有壞消息。好消息就是模板的類型推導是現代C++的最引人注目的特性:`auto`。如果你喜歡C++98模板的類型推導,那么你會喜歡上C++11的`auto`對應的模板類型推導。壞消息就是模板類型推導的法則是受限于`auto`的上下文的,有時候看起來應用到模板上不是那么直觀。因為這個原因,真正的理解模板`auto`的類型推導是很重要的。這條條款囊括了你的所需。 如果你想大致看一段偽碼,一段函數模板看起來會是這樣: ```cpp template<typename T> void f(ParamType param); ``` 調用會是這樣: ```cpp f(expr); // 用一些表達式來調用f ``` 在編譯的時候,編譯器通過`expr`來進行推導出兩個類型:一個是`T`的,另一個是`ParamType`。通常來說這些類型是不同的,因為`ParamType`通常包含一些類型的裝飾,比如`const`或引用特性。舉個例子,模板通常采用如下聲明: ```cpp template<typename T> void f(const T& param); // ParamType 是 const T& ``` 如果有這樣的調用: ```cpp int x = 0; f(x); // 使用int調用f ``` `T`被推導成`int`,`ParamType`被推導成`const int&`。 一般會很自然的期望`T`的類型和傳遞給他的參數的類型一致,也就是說`T`的類型就是`expr`的類型。在上面的例子中,`x`是一個`int`,`T`也就被推導成`int`。但是并不是所有的情況都是如此。`T`的類型不僅和`expr`的類型獨立,而且還和`ParamType`的形式獨立。下面是三個例子: * `ParamType`是一個指針或者是一個引用類型,但并不是一個通用的引用類型(通用的引用類型的內容在條款24。此時,你要知道例外情況會出現的,他們的類型并不和左值應用或者右值引用)。 * `ParamType`是一個通用的引用 * `ParamType`既不是指針也不是引用 這樣的話,我們就有了三種類型需要檢查的類型推導場景。每一種都是基于我們隊模板的通用的調用封裝: ```cpp template<typename T> void f(ParamType param); f(expr); // 從expr推導出T和ParamType的類型 ``` ###第一種情況:`ParamType`是個非通用的引用或者是一個指針 最簡單的情況是當`ParamType`是一個引用類型或者是一個指針,但并非是通用的引用。在這種情況下,類型推導的過程如下: 1. 如果`expr`的類型是個引用,忽略引用的部分。 2. 然后利用`expr`的類型和`ParamType`對比去判斷`T`的類型。 舉一個例子,如果這個是我們的模板, ```cpp template<typename T> void f(T& param); // param是一個引用類型 ``` 我們有這樣的代碼變量聲明: ```cpp int x = 27; // x是一個int const int cx = x; // cx是一個const int const int& rx = x; // rx是const int的引用 ``` `param`和`T`在不同的調用下面的類型推導如下: ```cpp f(x); // T是int,param的類型時int& f(cx); // T是const int, // param的類型是const int& f(rx); // T是const int // param的類型時const int& ``` 在第二和第三部分的調用,注意`cx`和`rx`由于被指定為`const`類型變量,`T`被推導成`const int`,這也就導致了參數的類型被推導為`const int&`。這對調用者非常重要。當傳遞一個`const`對象給一個引用參數,他們期望對象會保留常量特性,也就是說,參數變成了`const`的引用。這也就是為什么給一個以`T&`為參數的模板傳遞一個`const`對象是安全的:對象的`const`特性是`T`類型推導的一部分。 在第三個例子中,注意盡管`rx`的類型是一個引用,`T`仍然被推導成了一個非引用的。這是因為`rx`的引用特性會被類型推導所忽略。 這些例子展示了左值引用參數的處理方式,但是類型推導在右值引用上也是如此。當然,右值參數只可能傳遞給右值引用參數,但是這個限制和類型推導沒有關系。 如果我們把`f`的參數類型從`T&`變成`const T&`,情況就會發生變化,但是并不會令人驚訝。由于`param`的聲明是`const`引用的,`cx`和`rx`的`const`特性會被保留,這樣的話`T`的`const`特性就沒有必要了。 ```cpp template<typename T> void f(const T& param); // param現在是const的引用 int x = 27; // 和之前一樣 const int cx = x; // 和之前一樣 const int& rx = x; // 和之前一樣 f(x); // T是int,param的類型是const int& f(cx); // T是int,param的類型是const int& f(rx); // T是int,param的類型是const int& ``` 和之前一樣,`rx`的引用特性在類型推導的過程中會被忽略。 如果`param`是一個指針(或者指向`const`的指針)而不是引用,情況也是類似: ```cpp template<typename T> void f(T* param); // param是一個指針 int x = 27; // 和之前一樣 const int *px = &x; // px是一個指向const int x的指針 f(&x); // T是int,param的類型是int* f(px); // T是const int // param的類型時const int* ``` 到目前為止,你或許瞌睡了,因為C++在引用和指針上的類型推導法則是如此的自然,我寫出來讀者看顯得很沒意思。所有的事情都這么明顯!這就是讀者所期望的的類型推導系統吧。 ###第二種情況:`ParamType`是個通用的引用(Universal Reference) 對于通用的引用參數,情況就變得不是那么明顯了。這些參數被聲明成右值引用(也就是函數模板使用一個類型參數`T`,一個通用的引用參數的申明類型是`T&&`),但是當傳遞進去右值參數情況變得不一樣。完整的討論請參考條款24,這里是先行版本。 * 如果`expr`是一個左值,`T`和`ParamType`都會被推導成左值引用。這有些不同尋常。第一,這是模板類型`T`被推導成一個引用的唯一情況。第二,盡管`ParamType`利用右值引用的語法來進行推導,但是他最終推導出來的類型是左值引用。 * 如果`expr`是一個右值,那么就執行“普通”的法則(第一種情況) 舉個例子: ```cpp template<typename T> void f(T&& param); // param現在是一個通用的引用 int x = 27; // 和之前一樣 const int cx = x; // 和之前一樣 const int& rx = x; // 和之前一樣 f(x); // x是左值,所以T是int& // param的類型也是int& f(cx); // cx是左值,所以T是const int& // param的類型也是const int& f(rx); // rx是左值,所以T是const int& // param的類型也是const int& f(27); // 27是右值,所以T是int // 所以param的類型是int&& ``` 條款23解釋了這個例子推導的原因。關鍵的地方在于通用引用的類型推導法則和左值引用或者右值引用的法則大不相同。特殊的情況下,當使用了通用的引用,左值參數和右值參數的類型推導大不相同。這在非通用的類型推到上面絕對不會發生。 ###第三種情況:`ParamType`既不是指針也不是引用 當`ParamType`既不是指針也不是引用,我們把它處理成pass-by-value: ```cpp template<typename T> void f(T param); // param現在是pass-by-value ``` 這就意味著`param`就是完全傳給他的參數的一份拷貝——一個完全新的對象。基于這個事實可以從`expr`給出推導的法則: 1. 和之前一樣,如果`expr`的類型是個引用,將會忽略引用的部分。 2. 如果在忽略`expr`的引用特性,`expr`是個`const`的,也要忽略掉`const`。如果是`volatile`,照樣也要忽略掉(`volatile`對象并不常見。它們常常被用在實現設備驅動上面。查看更多的細節,請參考條款40。) 這樣的話: ```cpp int x = 27; // 和之前一樣 const int cx = x; // 和之前一樣 const int& rx = x; // 和之前一樣 f(x); // T和param的類型都是int f(cx); // T和param的類型也都是int f(rx); // T和param的類型還都是int ``` 注意盡管`cx`和`rx`都是`const`類型,`param`卻不是`const`的。這是有道理的。`param`是一個和`cx`和`rx`獨立的對象——一個`cx`和`rx`的拷貝。`cx`和`rx`不能被修改和`param`能不能被修改是沒有關系的。這就是為什么`expr`的常量特性(或者是易變性)(在很多的C++書籍上面`const`特性和`volatile`特性被稱之為CV特性——譯者注)在推導`param`的類型的時候被忽略掉了:`expr`不能被修改并不意味著它的一份拷貝不能被修改。 認識到`const`(和`volatile`)在按值傳遞參數的時候會被忽略掉。正如我們所見,引用的`const`或者是指針指向`const`,`expr`的`const`特性在類型推導的過程中會被保留。但是考慮到`expr`是一個`const`的指針指向一個`const`對象,而且`expr`被通過按值傳遞傳遞給`param`: ```cpp template<typename T> void f(T param); // param仍然是按值傳遞的(pass by value) const char* const ptr = // ptr是一個const指針,指向一個const對象 "Fun with pointers"; f(ptr); // 給參數傳遞的是一個const char * const類型 ``` 這里,位于星號右邊的`const`是表明指針是常量`const`的:`ptr`不能被修改指向另外一個不同的地址,并且也不能置成`null`。(星號左邊的`const`表明`ptr`指向的——字符串——是`const`的,也就是說字符串不能被修改。)當這個`ptr`傳遞給`f`,組成這個指針的內存bit被拷貝給`param`。這樣的話,指針自己(`ptr`)本身是被按值傳遞的。按照按值傳遞的類型推導法則,`ptr`的`const`特性會被忽略,這樣`param`的推導出來的類型就是`const char*`,也就是一個可以被修改的指針,指向一個`const`的字符串。`ptr`指向的東西的`const`特性被加以保留,但是`ptr`自己本身的`const`特性會被忽略,因為它要被重新復制一份而創建了一個新的指針`param`。 ##數組參數 這主要出現在mainstream的模板類型推導里面,但是有一種情況需要特別加以注意。就是數組類型和指針類型是不一樣的,盡管它們通常看起來是可以替換的。一個最基本的幻覺就是在很多的情況下,一個數組會被退化成一個指向其第一個元素的指針。這個退化的代碼常常如此: ```cpp const char name[] = "J. P. Briggs"; // name的類型是const char[13] const char * ptrToName = name; // 數組被退化成指針 ``` 在這里,`const char*`指針`ptrToName`使用`name`初始化,實際的`name`的類型是`const char[13]`。這些類型(`const char*`和`const char[13]`)是不一樣的,但是因為數組到指針的退化規則,代碼會被正常編譯。 但是如果一個數組傳遞給一個安置傳遞的模板參數里面情況會如何?會發生什么呢? ```cpp template<typename T> void f(T param); // 模板擁有一個按值傳遞的參數 f(name); // T和param的類型會被推到成什么呢? ``` 我們從一個沒有模板參數的函數開始。是的,是的,語法是合法的, ```cpp void myFunc(int param[]); // 和上面的函數相同 ``` 但是以數組聲明,但是還是把它當成一個指針聲明,也就是說`myFunc`可以和下面的聲明等價: ```cpp void myFunc(int* param); // 和上面的函數是一樣的 ``` 這樣的數組和指針等價的聲明經常會在以C語言為基礎的C++里面出現,這也就導致了數組和指針是等價的錯覺。 因為數組參數聲明會被當做指針參數,傳遞給模板函數的按值傳遞的數組參數會被退化成指針類型。這就意味著在模板`f`的調用中,模板參數`T`被推導成`const char*`: ```cpp f(name); // name是個數組,但是T被推導成const char* ``` 但是來一個特例。盡管函數不能被真正的定義成參數為數組,但是可以聲明參數是數組的引用!所以如果我們修改模板`f`的參數成引用, ```cpp template<typename T> void f(T& param); // 引用參數的模板 ``` 然后傳一個數組給他 ```cpp f(name); // 傳遞數組給f ``` `T`最后推導出來的實際的類型就是數組!類型推導包括了數組的長度,所以在這個例子里面,`T`被推導成了`const char [13]`,函數`f`的參數(數組的引用)被推導成了`const char (&)[13]`。是的,語法看起來怪怪的,但是理解了這些可以升華你的精神(原文knowing it will score you mondo points with those few souls who care涉及到了幾個宗教詞匯——譯者注)。 有趣的是,聲明數組的引用可以使的創造出一個推導出一個數組包含的元素長度的模板: ```cpp // 在編譯的時候返回數組的長度(數組參數沒有名字, // 因為只關心數組包含的元素的個數) template<typename T, std::size_t N> constexpr std::size_t arraySize(T (&)[N]) noexcept { return N; // constexpr和noexcept在隨后的條款中介紹 } ``` (`constexpr`是一種比`const`更加嚴格的常量定義,`noexcept`是說明函數永遠都不會拋出異常——譯者注) 正如條款15所述,定義為`constexpr`說明函數可以在編譯的時候得到其返回值。這就使得創建一個和一個數組長度相同的一個數組,其長度可以從括號初始化: ```cpp int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 }; // keyVals有七個元素 int mappedVals[arraySize(keyVals)]; // mappedVals長度也是七 ``` 當然,作為一個現代的C++開發者,應該優先選擇內建的`std::array`: ```cpp std::array<int, arraySize(keyVals)> mappedVals; // mappedVals長度是七 ``` 由于`arraySize`被聲明稱`noexcept`,這會幫助編譯器生成更加優化的代碼。可以從條款14查看更多詳情。 ##函數參數 數組并不是C++唯一可以退化成指針的東西。函數類型可以被退化成函數指針,和我們之前討論的數組的推導類似,函數可以被推華城函數指針: ```cpp void someFunc(int, double); // someFunc是一個函數 // 類型是void(int, double) template<typename T> void f1(T param); // 在f1中 參數直接按值傳遞 template<typename T> void f2(T& param); // 在f2中 參數是按照引用傳遞 f1(someFunc); // param被推導成函數指針 // 類型是void(*)(int, double) f2(someFunc); // param被推導成函數指針 // 類型時void(&)(int, double) ``` 這在實踐中極少有不同,如果你知道數組到指針的退化,或許你也就會就知道函數到函數指針的退化。 所以你現在知道如下:`auto`相關的模板推導法則。我把最重要的部分單獨在下面列出來。在通用引用中對待左值的處理有一點混亂,但是數組退化成指針和函數退化成函數指針的做法更加混亂呢。有時候你要對你的編譯器和需求大吼一聲,“告訴我到底類型推導成啥了啊!”當這種情況發生的時候,去參考條款4,因為它致力于讓編譯器告訴你是如何處理的。 |要記住的東西| | :--------- | | 在模板類型推導的時候,有引用特性的參數的引用特性會被忽略| | 在推導通用引用參數的時候,左值會被特殊處理| | 在推導按值傳遞的參數時候,`const`和/或`volatile`參數會被視為非`const`和非`volatile`| | 在模板類型推導的時候,參數如果是數組或者函數名稱,他們會被退化成指針,除非是用在初始化引用類型|
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看