<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國際加速解決方案。 廣告
                # [35] 模板 ? ## FAQs in section [35]: * [35.1] 模板的設計思想是什么? * [35.2] 什么是 “類模板”的語法/語義? * [35.3] 什么是“函數模板”的語法/語義? * [35.4] 如何確定顯式調用函數模板的哪個版本? * [35.5] 什么是“參數化類型”? * [35.6] 什么是“泛型”? * [35.7] 當模板類型T是 int 或std::string時,我的模板函數需要進行特殊處理。對特殊類型的T我該怎么實現模板特化? * [35.8] 哈?你能提供一個具體的模板特化的例子嗎?? * [35.9] 但是模板函數的大部分代碼是相同的,是否有辦法實現模板特化并且不用重復復制所有的源代碼? * [35.10] 所有這些模板和模板特化都會降低程序執行速度,對不對? * [35.11] 因此 模板重載了函數,對不對? * [35.12] 為什么不能分開模板的聲明和定義,把定義放到.cpp文件中? * [35.13] 如何避免模板函數的鏈接錯誤? * [35.14] 如何使用C++的關鍵字export來避免模板鏈接錯誤?? * [35.15] 如何避免模板類的鏈接錯誤? * [35.16] 為什么我收到鏈接錯誤 ,當我使用模板友元的時候? * [35.17] 怎么理解這些繁瑣的模板錯誤信息? * [35.18]當模板派生類使用一個繼承自模板基類的嵌套類型時,為什么出錯?? * [35.19]當模板派生類使用使用一個繼承自模板基類的成員變量時 ,為什么出錯?? * [35.20] 前一個問題可以暗傷我?難道編譯器默認地產生錯誤代碼?? ## 35.1 模板的設計思想是什么? 模板像是甜餅切割器,指定如何切割cookies讓他們看起來大致相同(雖然Cookie由各種面團來制作,但是他們都會有相同的基本形狀)。同樣,類模板是描述如何建立一個類族,讓所有的類看起來是基本相同;函數模板描述如何建立一個外觀類似的函數族。 類模板通常用于構建類型安全的容器(although this only scratches the surface for how they can be used)。 ## 35.2 什么是 “類模板”的語法/語義? 考慮一個容器`類class Array`,它的行為像一個整數數組: ``` //?This?would?go?into?a?header?file?such?as?"__Array.h__" ?class?Array?{ ?public: ???Array(int?len=10)??????????????????:?len_(len),?data_(new?int[len])?{?} ??~Array()????????????????????????????{?delete[]?data_;?} ???int?len()?const????????????????????{?return?len_;?????} ???const?int&?operator[](int?i)?const?{?return?data_[check(i)];?}??←?subscript?operators?often?come?in?pairs What's the deal with "const-overloading"?") ?????????int&?operator[](int?i)???????{?return?data_[check(i)];?}??←?subscript?operators?often?come?in?pairs What's the deal with "const-overloading"?") ???Array(const?Array&); ???Array&?operator=?(const?Array&); ?private: ???int??len_; ???int*?data_; ???int??check(int?i)?const ?????{?if?(i?<?0?||?i?>=?len_)?throw?BoundsViol("Array",?i,?len_); ???????return?i;?} ?}; ``` 對于浮點數數組,字符數組,`std::string`數組,`std::string`數組的數組等,反復重復上述步驟將很冗長乏味。 ``` //?This?would?go?into?a?header?file?such?as?"__Array.h__" ?template<typename?T> ?class?Array?{ ?public: ???Array(int?len=10)????????????????:?len_(len),?data_(new?T[len])?{?} ??~Array()??????????????????????????{?delete[]?data_;?} ???int?len()?const??????????????????{?return?len_;?????} ???const?T&?operator[](int?i)?const?{?return?data_[check(i)];?} ?????????T&?operator[](int?i)???????{?return?data_[check(i)];?} ???Array(const?Array<T>&); ???Array<T>&?operator=?(const?Array<T>&); ?private: ???int?len_; ???T*??data_; ???int?check(int?i)?const ?????{?if?(i?<?0?||?i?>=?len_)?throw?BoundsViol("Array",?i,?len_); ???????return?i;?} ?}; ``` 與模板函數不同,模板類(實例化模板)在實例化時需要指明相關參數: ``` ?int?main() ?{ ???Array<int>???????????ai; ???Array<float>?????????af; ???Array<char*>?????????ac; ???Array<std::string>???as; ???Array<?Array<int>?>??aai; ... ?} ``` 注意最后一個例子中的兩個`>`之間的空格符。如果沒有這個空格符,編譯器會看到一個`>>`(右移位)標記,而不是兩個`>`。 ## 35.3 什么是“函數模板”的語法/語義? 考慮下面函數,交換兩個整型參數: ``` ?void?swap(int&?x,?int&?y) ?{ ???int?tmp?=?x; ???x?=?y; ???y?=?tmp; ?} ``` 如果我們還要交換浮點數,長整形,字符串,集合,和文件系統等,我們就會疲于編寫除了類型不同的相似的編碼行。重復是電腦理想的工作,因此要用函數模板: ``` ?template<typename?T> ?void?swap(T&?x,?T&?y) ?{ ???T?tmp?=?x; ???x?=?y; ???y?=?tmp; ?} ``` 對給定的類型每次我們使用swap()的時候,編譯器將根據上述定義,并自動產生另外一個“模板函數”作為上述函數模板的實例化。例如: ``` ?int?main() ?{ ???int?????????i,j;??/*...*/??swap(i,j);??//?Instantiates?a?swap?for?int ???float???????a,b;??/*...*/??swap(a,b);??//?Instantiates?a?swap?for?float ???char????????c,d;??/*...*/??swap(c,d);??//?Instantiates?a?swap?for?char ???std::string?s,t;??/*...*/??swap(s,t);??//?Instantiates?a?swap?for?std::string ... ?} ``` 注:“模板函數”是一個“函數模板”的實例化形態。 ## 35.4 如何確定顯式調用函數模板的哪個版本? 當你調用一個函數模板時,編譯器試圖推斷模板類型。大部分情況下,編譯器可以成功的做到這一點,但有時你可能想要幫助編譯器推斷出正確的類型-要么是因為它不能推斷出模板類型,或者是因為它會推斷出錯誤類型。 例如,你可能會調用一個函數模板沒有模板指定的參數類型,或者你可能想讓編譯器在選擇正確的函數模板之前,迫使它對參數做一些轉換(promotions)。在這些情況下,你需要明確地告訴編譯器應該調用函數的模板哪個實例化。 下面是一個示例函數模板,模板參數 T沒有出現在函數的參數列表中。在這種情況下, 編譯器無法推斷出模板參數類型在函數被調用時。 ``` ?template<typename?T> ?void?f() ?{ ... ?} ``` 若要調用該函數把 `T`作為`int`或`std::string`,你可以這樣做: ``` ?#include?<string> ?void?sample() ?{ ???f<int>();??????????//?type?T?will?be?int?in?this?call ???f<std::string>();??//?type?T?will?be?std::string?in?this?call ?} ``` 這里是另一個函數,它的模板參數出現在函數的正式參數列表中(也就是說,編譯器_可以_根據實際參數的類型推導出模板類型): ``` ?template<typename?T> ?void?g(T?x) ?{ ... ?} ``` 現在如果你想強制實行參數轉換,在編譯器推斷模板類型之前,你可以使用上述技術。例如,如果你只是簡單調用`g(42)`,你會得到`g<int>(42)`,但如果你想傳遞42給`g<long>()`,你可以這樣做: `g<long>(42)`。(當然你也可以明確地轉換參數,如可以`g(long(42))`,甚至`g(42L)`,當然如果這樣的話本例子就沒有什么意義了。) 同樣,如果你調用`g(“xyz”)`,你最終會調用`g<char*>(char*)`,但如果你想調用`std::string`版本`g<>()`,你可以這樣`g<std::string>(”xyz“)`。(同樣你也可以轉換參數,例如`g(std::string(“xyz”)`,不過那將是另一回事。) ## 35.5 什么是“參數化類型”? 換句話說,“類模板”。 參數化類型是一個類型,是參數化的類型或者值。 `list<int>`是一個被另外一個類型(`int`)參數化的類型( `List` )。 ## 35.6 什么是“泛型”? 還是“類模板”另一種說法。 不要 與“一般性(generality)”混淆(“一般性(generality)”這只是避免過于具體的解決方案),“泛型”是指類模板。 ## 35.7 當模板類型`T`是 `int`或`std::string`時,我的模板函數需要進行特殊處理。對特殊類型的T我該怎么實現模板特化? 在展示如何做到這一點之前,讓我們確保你不會搬起石頭砸自己的腳。對于用戶來說是否該函數的行為不同?換言之,是否可以觀察到的行為有實質性的不同?如果是這樣,你可能是在自找苦吃,你可能迷惑用戶--你最好使用不同名稱的函數--不要使用模板,不要使用重載。例如,如果接受`int`類型的代碼要插入一些東西到容器并且對結果排序,但接受`std::string`類型的代碼要從容器中刪除東西并且不對結果排序,這兩個函數不應該是可以重載的函數對--他們可以觀察的行為是不同的,所以他們應該有不同的函數名稱。 但是,如果該函數的可觀察到的行為是一致的,對于所有T類型僅僅局限在各自實現細節上的不同,那么就請繼續讀下去。讓我們看看這方面的一個例子(僅僅是概念上,不是C++代碼): ``` ?template<typename?T> ?void?foo(const?T&?x) ?{ ???switch?(typeof(T))?{??←?conceptual?only;?not?C++ ?????case?int: ???????...??←?implementation?details?when?T?is?int ???????break; ?????case?std::string: ???????...??←?implementation?details?when?T?is?std::string ???????break; ?????default: ???????...??←?implementation?details?when?T?is?neither?int?nor?std::string ???????break; ???} ?} ``` 解決上述問題的辦法就是是通過模板特化。不要使用`switch`語句,你需要把代碼分解成單獨的函數。第一個函數是默認的情況--當 `T`是`int`或`std::string`以外的任何其他類型時候的代碼: ``` ?template<typename?T> ?void?foo(const?T&?x) ?{ ???...??←?implementation?details?when?T?is?neither?int?nor?std::string ?} ``` 下一步是兩個特例,第一個是`int`特例 的代碼: ``` ?template<> ?void?foo<int>(const?int&?x) ?{ ???...??←?implementation?details?when?T?is?int ?} ``` 接著是`std::string`特例 的代碼: ``` ?template<> ?void?foo<std::string>(const?std::string&?x) ?{ ???...??←?implementation?details?when T?is?std::string ?} ``` 好啦,大功告成!編譯器將自動選擇正確的特例實現根據所使用的`T`的類型。 ## 35.8 哈?你能提供一個具體的模板特化的例子嗎?? 可以。 下面我個人使用模板特化的幾種常見情況是字符串化。我通常使用模板, 將不同類型的對象字符串化,但通常需要字符串化某些特定的類型,例如當字符串化 布爾變量的時候,我喜歡用“true”與“false”來代替“1”和“0”,所以當 T 是布爾類型時,我使用std::boolalpha 。此外,我喜歡浮點輸出包含所有的數字(這樣我就可以看得很小的差異,等等),因此當 T是一個浮點類型時候,我使用`std::setprecision`。最終的結果通常如下所示: ``` ?#include?<iostream> ?#include?<sstream> ?#include?<iomanip> ?#include?<string> ?#include?<limits> ?template<typename?T>?inline?std::string?stringify(const?T&?x) ?{ ???std::ostringstream?out; ???out?<<?x; ???return?out.str(); ?} ?template<>?inline?std::string?stringify<bool>(const?bool&?x) ?{ ???std::ostringstream?out; ???out?<<?std::boolalpha?<<?x; ???return?out.str(); ?} ?template<>?inline?std::string?stringify<double>(const?double&?x) ?{ ???const?int?sigdigits?=?std::numeric_limits<double>::digits10; //?or?perhaps?std::numeric_limits<double>::max_digits10?if?that?is?available?on?your?compiler ???std::ostringstream?out; ???out?<<?std::setprecision(sigdigits)?<<?x; ???return?out.str(); ?} ?template<>?inline?std::string?stringify<float>(const?float&?x) ?{ ???const?int?sigdigits?=?std::numeric_limits<float>::digits10; //?or?perhaps?std::numeric_limits<float>::max_digits10?if?that?is?available?on?your?compiler ???std::ostringstream?out; ???out?<<?std::setprecision(sigdigits)?<<?x; ???return?out.str(); ?} ?template<>?inline?std::string?stringify<long?double>(const?long?double&?x) ?{ ???const?int?sigdigits?=?std::numeric_limits<long?double>::digits10; //?or?perhaps?std::numeric_limits<long_double>::max_digits10?if?that?is?available?on?your?compiler ???std::ostringstream?out; ???out?<<?std::setprecision(sigdigits)?<<?x; ???return?out.str(); ?} ``` 從概念上來講他們都做同樣的事情:把參數字符串化。這意味著可觀察的行為是一致的,因此特化不會迷惑用戶。但對于`bool`和浮點類型,細節的實現略有不同,因此模板特化是一個好的解決方法。 ## 35.9 但是模板函數的大部分代碼是相同的,是否有辦法實現模板特化并且不用重復復制所有的源代碼? 是。 例如,假設你的模板函數有很多共同的代碼,與類型T相關的特定代碼相對很少(僅僅是概念展示;不是C++): ``` ?template<typename?T> ?void?foo(const?T&?x) ?{ ...?common?code?that?works?for?all?T?types?... ???switch?(typeof(T))?{??←?conceptual?only;?not?C++ ?????case?int: ...?small?amount?of?code?used?only?when?T?is?int?... ???????break; ?????case?std::string: ...?small?amount?of?code?used?only?when?T?is?std::string... ???????break; ?????default: ...?small?amount?of?code?used?when?T?is?neither?int?nor?std::string?... ???????break; ???} ...?more?common?code?that?works?for?all?T?types?... ?} ``` 如果盲目地跟從模板特化FAQ的建議,你最終將需要重復`switch`語句之前和之后的所有代碼。兩全其美的方式—既不重復相同代碼又可以實現`T`的特定代碼,是分離`switch`語句到一個單獨的函數`foo_part()`,并使用模板特殊化: ``` ?template<typename?T>?inline?void?foo_part(const?T&?x) ?{ ...?small?amount?of?code?used?when?T?is?neither?int?nor?std::string?... ?} ?template<>?inline?void?foo_part<int>(const?int&?x) ?{ ...?small?amount?of?code?used?only?when?T?is?int?... ?} ?template<>?inline?void?foo_part<std::string>(const?std::string&?x) ?{ ...?small?amount?of?code?used?only?when?T?is?std::string?... ?} ``` 主要的`foo()`函數是一個簡單的模板-沒有特化。請注意,`switch`語句已經被替換為`foo_part()`調用: ``` ?template<typename?T> ?void?foo(const?T&?x) ?{ ...?common?code?that?works?for?all?T?types?... ???foo_part(x); ...?more?common?code?that?works?for?all?T?types?... ?} ``` 正如你所看到的, `foo()`的函數體本身并沒有任何特殊,這一切都會自動的被調用。編譯器自動生成的基于 `T`類型 的`foo()`,并會生成正確的`foo_part`函數,根據實際編譯時的`X`的參數類型。合適的`foo_part`的特化會被實例化。 ## 35.10 所有這些模板和模板特化都會降低程序執行速度,對不對? 錯誤的。 這與實現代碼的質量有關,結果可能會有所不同。但是不會有任何降低。模板可能會些微影響編譯速度,但一旦類型在編譯時被確定,它通常會生成和非模板函數(包括內聯展開等)一樣快的代碼。 ## 35.11 因此模板重載了函數,對不對? 是也不是。 函數模板參與重載函數的名稱解析,但規則是不同的。對于模板重載,類型需要完全匹配。如果類型不完全匹配,類型不會被轉換,函數模板從可行的函數集合中被排除。這就是所謂的“SFINAE”- Subsitution Failure Is Not An Error。例如: ``` ?#include?<iostream> ?#include?<typeinfo> ?template<typename?T>?void?foo(T*?x) ?{?std::cout?<<?"foo<"?<<?typeid(T).name()?<<?">(T*)\n";?} ?void?foo(int?x) ?{?std::cout?<<?"foo(int)\n";?} ?void?foo(double?x) ?{?std::cout?<<?"foo(double)\n";?} ?int?main() ?{ ?????foo(42);????????//?matches?foo(int)?exactly ?????foo(42.0);??????//?matches?foo(double)?exactly ?????foo("abcdef");??//?matches?foo<T>(T*)?with?T?=?char ?????return?0; ?} ``` 在這個例子中, 在main()函數中第一或第二次調用`foo`不是對`foo<T>`的調用,因為無論42還是42.0都沒有提供給編譯器的任何信息來推斷 。然而第三個調用,包括`foo<T>`并且`T = char`,因此它會調用`foo<T>`。 ## 35.12 為什么不能分開模板的聲明和定義,把定義放到`.cpp`文件中? 如果你想知道的是只是如何解決這種情況,請閱讀下面得兩個s。但是,為了理解要那樣,首先接受這些事實: 1. 模板是不是一個類或函數。 模板是一個“模式”,編譯器用來生成的相似的類或者函數。 2. 為了讓編譯器生成的代碼,它必須同時看到模板的定義(不只是聲明)和特定類型/任何用于“fill in”模板的類型。例如,如果你想使用一個`foo<int>`,編譯器必須同時看到foo模板和你要調用具體的`foo<int>`。 3. 編譯器可能不記得另外一個`.cpp`文件的細節,當編譯其他`.cpp`文件的時候。它可以 ,但大多數都沒有,如果你正在閱讀本FAQ,它幾乎肯定不會。順便說一句,這就是所謂的“獨立編譯模型”。 現在,基于這些事實,下面是一個范例,它表明為什么是這個樣子。假設你有一個這樣的模板`Foo`聲明: ``` ?template<typename?T> ?class?Foo?{ ?public: ???Foo(); ???void?someMethod(T?x); ?private: ???T?x; ?}; ``` 類似地,模板成員函數的定義: ``` ?template<typename?T> ?Foo<T>::Foo() ?{ ... ?} ?template<typename?T> ?void?Foo<T>::someMethod(T?x) ?{ ... ?} ``` 現在,假設在文件`Bar.cpp`的一些代碼要使用`foo<int>`: ``` //?Bar.cpp ?void?blah_blah_blah() ?{ ... ???Foo<int>?f; ???f.someMethod(5); ... ?} ``` 顯然,某人某地將不得不調用“模式”的構造函數,和`someMethod()`函數以及做`T`為`int`的實例化。但是,如果你把構造函數和`someMethod()`的定義放到文件`Foo.cpp`,當編譯`Foo.cpp`時,編譯器將看到模板代碼;當編譯`Bar.cpp`時,編譯器將看到`foo<int>`。但任何時候決不會同時看到模板代碼和`foo<int>`。因此,通過上面的2號規則,它根本不會產生`foo <int>::someMethod()`的代碼。 _寫給專家們的話:很明顯我對以上內容作了簡化。這是有意為之,所以請不要大聲抱怨。_如果你知道`.cpp`文件和編譯單元的差別,類模板和模板類的差別,模板其實不只是美化的宏等,請不要抱怨:這個問題/解答不是為你而設。我簡化它是為了新手能夠“理解它”,即使這樣可能會冒犯一些專家。 _提醒:_欲知解決方案,請閱讀下面得兩個 FAQs。 ## 35.13 如何避免模板函數的鏈接錯誤? 當編譯模板函數的`.cpp`文件的時候告訴C++編譯器應該使用哪個實例。 例如,考慮`foo.h`頭文件包含以下模板函數聲明: ``` //?File?"foo.h" ?template<typename?T> ?extern?void?foo(); ``` 現在假設文件`foo.cpp`實際上定義的模板函數: ``` //?File?"foo.cpp" ?#include?<iostream> ?#include?"foo.h" ?template<typename?T> ?void?foo() ?{ ???std::cout?<<?"Here?I?am!\n"; ?} ``` 假設文件`main.cpp`中使用這個模板函數通過調用`foo<int>()`: ``` //?File?"main.cpp" ?#include?"foo.h" ?int?main() ?{ ???foo<int>(); ... ?} ``` 如果你編譯和(試圖)鏈接這兩個`.cpp`文件,大多數編譯器將生成鏈接錯誤。有三種的解決方案。第一個解決方案是物理上在`.h`文件中定義,即使它不是一個內聯函數。這種解決辦法可能(或可能不會!)造成重大代碼膨脹,意味著可執行文件的大小可能會顯顯著增加(或者,如果你的編譯器足夠聰明,可能不會這么做)。 另一個解決辦法是保留定義在`.cpp`文件中,只添加行`template void foo<int>()`到`.cpp`文件: ``` //?File?"foo.cpp" ?#include?<iostream> ?#include?"foo.h" ?template<typename?T>?void?foo() ?{ ???std::cout?<<?"Here?I?am!\n"; ?} ?template?void?foo<int>(); ``` 如果你不能修改`foo.cpp`,只需創建一個新的`.cpp`文件,例如`foo-impl.cpp`如下: ``` //?File?"foo-impl.cpp" ?#include?"foo.cpp" ?template?void?foo<int>(); ``` 請注意, `foo-impl.cpp`文件包含`.cpp`文件,而不是`.h`文件。如果你覺著這樣很亂,跳個踢踏舞,想想堪薩斯,跟著我重復,“我要這么做即使它很混亂。” 你需要信任我。如果不信任或者致使好奇,前面的FAQ給出了理由。 ## 35.14 如何使用C++的關鍵字`export`來避免模板鏈接錯誤?? C++關鍵字`export`是設計用來消除包含一個模板定義(無論是在頭文件中或通過實現文件中)的需要。但是,在寫這篇文章時,支持此功能的唯一的知名編譯器,是[Comeau C++](http://www.comeaucomputing.com/tryitout)。`export`關鍵字未來還是個未知數。說句公道話,一些編譯器廠商表示他們可能永遠不會實現它,而C++標準委員會已決定大家自己定奪。 在不支持關鍵字`export`的編譯器上,如果你希望你的代碼可以通過編譯,并且還希望能夠有效利用支持`export`關鍵字的編譯器。你可以這樣定義模板頭文件: ``` //?File?Foo.h ?template<typename?T> ?class?Foo?{ ... ?}; ?#ifndef?USE_EXPORT_KEYWORD ???#include?"Foo.cpp" ?#endif ``` 并定義非內聯函數的源代碼文件如下: ``` //?File?Foo.cpp ?#ifndef?USE_EXPORT_KEYWORD ???#define?export?/*nothing*/ ?#endif ?export?template<typename?T>?... ``` 然后,如果/當你的編譯器支持`export`關鍵字的時候,并且因為某些原因你想利用該功能,只要定義符號`USE_EXPORT_KEYWORD`即可。 要訣就是,你現在可以開發程序, 好像你的編譯器已經實現了`export`關鍵字。如果/當你的編譯器真正支持該關鍵字的時候,只需要定義`USE_EXPORT_KEYWORD`標志,重新編譯,馬上你就可以利用該功能。 ## 35.15 如何避免模板類的鏈接錯誤? 當編譯模板類的`.cpp`文件得手告訴你的C++編譯器應該使用哪個模板實例。(如果你已經閱讀以前的問題,答案是完全一樣的,所以你也許可以跳過此答案。) 作為一個例子,考慮`Foo.h`頭文件包含以下模板類。請注意, `Foo<T>::f()`方法是內聯的,而`Foo<T>::g()`和`Foo<T>::h()`卻不是。 ``` //?File?"Foo.h" ?template<typename?T> ?class?Foo?{ ?public: ???void?f(); ???void?g(); ???void?h(); ?}; ?template<typename?T> ?inline ?void?Foo<T>::f() ?{ ... ?} ``` 現在,假設文件`Foo.cpp`實際定義了非內聯的`Foo<T>::g()`和`Foo<T>::h()`: ``` //?File?"Foo.cpp" ?#include?<iostream> ?#include?"Foo.h" ?template<typename?T> ?void?Foo<T>::g() ?{ ???std::cout?<<?"Foo<T>::g()\n"; ?} ?template<typename?T> ?void?Foo<T>::h() ?{ ???std::cout?<<?"Foo<T>::h()\n"; ?} ``` 假設文件`main.cpp`使用該模板創建一個`Foo<int>`并調用其方法: ``` //?File?"main.cpp" ?#include?"Foo.h" ?int?main() ?{ ???Foo<int>?x; ???x.f(); ???x.g(); ???x.h(); ... ?} ``` 如果你編譯和(試圖)鏈接這兩個`.cpp`文件,大多數編譯器將生成鏈接錯誤。有三種的解決方案。第一個解決方案是物理上在`.h`文件中定義,即使它不是一個內聯函數。這種解決辦法可能(或可能不會!)造成重大代碼膨脹,意味著可執行文件的大小可能會顯顯著增加(或者,如果你的編譯器足夠聰明,可能不會這么做)。 另一個解決辦法是保留定義在`.cpp`文件中,只添加行`template class Foo<int>;`到`.cpp`文件: ``` //?File?"Foo.cpp" ?#include?<iostream> ?#include?"Foo.h" ...definition?of?Foo<T>::f()?is?unchanged?--?see?above... ...definition?of?Foo<T>::g()?is?unchanged?--?see?above... ?template?class?Foo<int>; ``` 如果你不能修改`foo.cpp`,只需創建一個新的`.cpp`文件,例如`foo-impl.cpp`如下: ``` //?File?"Foo-impl.cpp" ?#include?"Foo.cpp" ?template?class?Foo<int>; ``` 請注意, `foo-impl.cpp`文件包含`.cpp`文件,而不是`.h`文件。如果你覺著這樣很亂,跳個踢踏舞,想想堪薩斯,跟著我重復,“我要這么做即使它很混亂。” 你需要信任我。如果不信任或者致使好奇,前面的FAQ給出了理由。 如果你使用[Comeau C++](http://www.comeaucomputing.com/tryitout "www.comeaucomputing.com/tryitout"),你可能使用`export`關鍵字實現類似功能。 ## 35.16 為什么我收到鏈接錯誤 ,當我使用模板友元的時候? 由于模板友類的復雜性。下面是一個常見的例子: ``` ?#include?<iostream> ?template<typename?T> ?class?Foo?{ ?public: ???Foo(const?T&?value?=?T()); ???friend?Foo<T>?operator+?(const?Foo<T>&?lhs,?const?Foo<T>&?rhs); ???friend?std::ostream&?operator<<?(std::ostream&?o,?const?Foo<T>&?x); ?private: ???T?value_; ?}; ``` 當然在某個地方我們會用到模板: ``` ?int?main() ?{ ???Foo<int>?lhs(1); ???Foo<int>?rhs(2); ???Foo<int>?result?=?lhs?+?rhs; ???std::cout?<<?result; ... ?} ``` 當然,在某個地方需要定義各成員和友元函數: ``` ?template<typename?T> ?Foo<T>::Foo(const?T&?value?=?T()) ???:?value_(value) ?{?} ?template<typename?T> ?Foo<T>?operator+?(const?Foo<T>&?lhs,?const?Foo<T>&?rhs) ?{?return?Foo<T>(lhs.value_?+?rhs.value_);?} ?template<typename?T> ?std::ostream&?operator<<?(std::ostream&?o,?const?Foo<T>&?x) ?{?return?o?<<?x.value_;?} ``` 一個潛在問題是編譯器如何理解類聲明中的friends行。在看到friends行的時候,它還不知道友元函數本身也是模板,它假定他們不是模板函數,就像下面這樣: ``` ?Foo<int>?operator+?(const?Foo<int>&?lhs,?const?Foo<int>&?rhs) ?{?...?} ?std::ostream&?operator<<?(std::ostream&?o,?const?Foo<int>&?x) ?{?...?} ``` 當你調用運算符`+`或運算符`<<`的時候,這種假設導致編譯器生成一個對非模板函數的調用,但是鏈接器會給你一個“未定義的外部函數”錯誤,因為你從來沒有真正的定義這些非模板函數。 解決的辦法是在編譯器編譯類體的時候,讓編譯器知道運算符`+`和運算符`<<`本身是模板。有幾種方法可以做到這一點;一個簡單的方法是在定義函數模板類 Foo的時候預先聲明模板友元: ``` ?template<typename?T>?class?Foo;??//?pre-declare?the?template?class?itself ?template<typename?T>?Foo<T>?operator+?(const?Foo<T>&?lhs,?const?Foo<T>&?rhs); ?template<typename?T>?std::ostream&?operator<<?(std::ostream&?o,?const?Foo<T>&?x); ``` 在`frend`行中你也需要加入`<>`,如下所示: ``` ?#include?<iostream> ?template<typename?T> ?class?Foo?{ ?public: ???Foo(const?T&?value?=?T()); ???friend?Foo<T>?operator+?<>?(const?Foo<T>&?lhs,?const?Foo<T>&?rhs); ???friend?std::ostream&?operator<<?<>?(std::ostream&?o,?const?Foo<T>&?x); ?private: ???T?value_; ?}; ``` 這些寫法將有助于編譯器更好地了解友元函數。值得一提的是,它會發現友元函數本身是模板。這消除了混亂。 另一種方法是在類中同時聲明和定義該友元函數。例如: ``` ?#include?<iostream> ?template<typename?T> ?class?Foo?{ ?public: ???Foo(const?T&?value?=?T()); ???friend?Foo<T>?operator+?(const?Foo<T>&?lhs,?const?Foo<T>&?rhs) ???{ ... ???} ???friend?std::ostream&?operator<<?(std::ostream&?o,?const?Foo<T>&?x) ???{ ... ???} ?private: ???T?value_; ?}; ``` ## 35.17 怎么理解這些繁雜的模板錯誤信息? 這里有一個免費工具, [可以轉換錯誤信息便于理解](http://www.bdsoft.com/tools/stlfilt.html)。在撰寫本文的時候,它工作用于下列編譯器:Comeau C +,Intel C++,CodeWarrior C++,gcc,Borland C++,Microsoft Visual C++和EDG C++。 這里有一個例子,下面是一些原始的gcc的錯誤信息: ``` ?rtmap.cpp:?In?function?int?main()': ?rtmap.cpp:19:?invalid?conversion?from?int'?to? ????std::_Rb_tree_node<std::pair<const?int,?double>?>*' ?rtmap.cpp:19:???initializing?argument?1?of?std::_Rb_tree_iterator<_Val,?_Ref, ????_Ptr>::_Rb_tree_iterator(std::_Rb_tree_node<_Val>*)?[with?_Val?= ????std::pair<const?int,?double>,?_Ref?=?std::pair<const?int,?double>&,?_Ptr?= ????std::pair<const?int,?double>*]' ?rtmap.cpp:20:?invalid?conversion?from?int'?to? ????std::_Rb_tree_node<std::pair<const?int,?double>?>*' ?rtmap.cpp:20:???initializing?argument?1?of?std::_Rb_tree_iterator<_Val,?_Ref, ????_Ptr>::_Rb_tree_iterator(std::_Rb_tree_node<_Val>*)?[with?_Val?= ????std::pair<const?int,?double>,?_Ref?=?std::pair<const?int,?double>&,?_Ptr?= ????std::pair<const?int,?double>*]' ?E:/GCC3/include/c++/3.2/bits/stl_tree.h:?In?member?function?void ????std::_Rb_tree<_Key,?_Val,?_KeyOfValue,?_Compare,?_Alloc>::insert_unique(_II, ?????_II)?[with?_InputIterator?=?int,?_Key?=?int,?_Val?=?std::pair<const?int, ????double>,?_KeyOfValue?=?std::_Select1st<std::pair<const?int,?double>?>, ????_Compare?=?std::less<int>,?_Alloc?=?std::allocator<std::pair<const?int, ????double>?>]': ?E:/GCC3/include/c++/3.2/bits/stl_map.h:272:???instantiated?from?void?std::map<_ ?Key,?_Tp,?_Compare,?_Alloc>::insert(_InputIterator,?_InputIterator)?[with?_Input ?Iterator?=?int,?_Key?=?int,?_Tp?=?double,?_Compare?=?std::less<int>,?_Alloc?=?st ?d::allocator<std::pair<const?int,?double>?>]' ?rtmap.cpp:21:???instantiated?from?here ?E:/GCC3/include/c++/3.2/bits/stl_tree.h:1161:?invalid?type?argument?of?unary?* ????' ``` 以下是經過過濾的錯誤信息(注:你可以配置工具讓它顯示更多的信息,下面輸出的設置是剪裁信息到最少): ``` ?rtmap.cpp:?In?function?int?main()': ?rtmap.cpp:19:?invalid?conversion?from?int'?to?iter' ?rtmap.cpp:19:???initializing?argument?1?of?iter(iter)' ?rtmap.cpp:20:?invalid?conversion?from?int'?to?iter' ?rtmap.cpp:20:???initializing?argument?1?of?iter(iter)' ?stl_tree.h:?In?member?function?void?map<int,double>::insert_unique(_II,?_II)': ?????[STL?Decryptor:?Suppressed?1?more?STL?standard?header?message] ?rtmap.cpp:21:???instantiated?from?here ?stl_tree.h:1161:?invalid?type?argument?of?unary?*' ``` 以下是上面例子的源代碼: ``` ?#include?<map> ?#include?<algorithm> ?#include?<cmath> ?const?int?values[]?=?{?1,2,3,4,5?}; ?const?int?NVALS?=?sizeof?values?/?sizeof?(int); ?int?main() ?{ ?????using?namespace?std; ?????typedef?map<int,?double>?valmap; ?????valmap?m; ?????for?(int?i?=?0;?i?<?NVALS;?i++) ?????????m.insert(make_pair(values[i],?pow(values[i],?.5))); ?????valmap::iterator?it?=?100;??????????????//?error ?????valmap::iterator?it2(100);??????????????//?error ?????m.insert(1,2);??????????????????????????//?error ?????return?0; ?} ``` ## 35.18 當模板派生類使用一個繼承自模板基類的嵌套類型時,為什么出錯?? 你也許很吃驚,下面的代碼是無效的C++代碼,即使如此通過有些編譯器: ``` ?template<typename?T> ?class?B?{ ?public: ???class?Xyz?{?...?};??←?type?nested?in?class?B<T> ???typedef?int?Pqr;????←?type?nested?in?class?B<T> ?}; ?template<typename?T> ?class?D?:?public?B<T>?{ ?public: ???void?g() ???{ ?????Xyz?x;??←?bad?(even?though?some?compilers?erroneously?(temporarily?)?accept?it) ?????Pqr?y;??←?bad?(even?though?some?compilers?erroneously?(temporarily?)?accept?it) ???} ?}; ``` 這可能會讓你很傷腦筋,最好坐下來聽我講。 在函數`D<T>::g()`內,名字`xyz`和`Pqr`不依賴于模板參數`T`,所以他們被稱作為nondependent名字。另一方面`B<T>` 依賴模板參數`T`,因此 `B<T>` 稱作_dependent名字_。 規則是這樣的:當查找nondependent名字(比如`Xyz`和`Pqr`)的時候,編譯器不會查找dependent基類(如`B <T>`中 )。因此,編譯器不知道他們甚至還存在,更不用說知道它們也是類型。 這時,程序員有時會添加前綴`B <T>::`,例如: ``` ?template<typename?T> ?class?D?:?public?B<T>?{ ?public: ???void?g() ???{ ?????B<T>::Xyz?x;??←?bad?(even?though?some?compilers?erroneously?(temporarily?)?accept?it) ?????B<T>::Pqr?y;??←?bad?(even?though?some?compilers?erroneously?(temporarily?)?accept?it) ???} ?}; ``` 可惜這也行不通,因為這些名字(你準備好了嗎?坐下來?)不一定是類型。 "哈?!?" ?"不是類型?!?" ?。“太搞了吧!任何傻瓜都可以看到他們是類型;只要看上一眼!”,你抗議。抱歉,事實是,他們可能不是類型。原因是,有可能是`B<T>`的特化,假設`B<Foo>`,其中 `B <Foo>::Xyz`是一個數據成員。由于這種潛在的特化,編譯器不能假設`B<T>::Xyz`是一個類型,直到它知道`T `。解決方案是通過`typename`關鍵字提示編譯器: ``` ?template<typename?T> ?class?D?:?public?B<T>?{ ?public: ???void?g() ???{ ?????typename?B<T>::Xyz?x;??←?good ?????typename?B<T>::Pqr?y;??←?good ???} ?}; ``` ## 35.19 當模板派生類使用使用一個繼承自模板基類的成員變量時 ,為什么出錯? ? 你也許很吃驚,下面的代碼是無效的C++代碼,即使如此通過有些編譯器: ``` ?template<typename?T> ?class?B?{ ?public: ???void?f()?{?}??←?member?of?class?B<T> ?}; ?template<typename?T> ?class?D?:?public?B<T>?{ ?public: ???void?g() ???{ ?????f();??←?bad?(even?though?some?compilers?erroneously?(temporarily?)?accept?it) ???} ?}; ``` 這可能會讓你很傷腦筋,最好坐下來聽我講。 在函數`D<T>::g()`內,名字`f`不依賴于模板參數`T`,所以他們被稱作為nondependent名字。另一方面`B<T>` 依賴模板參數`T`,因此 `B<T>` 稱作_dependent名字_。 規則是這樣的:當查找nondependent名字(比如f)的時候,編譯器不會查找dependent基類(如`B <T>`中 )。 這并不意味著繼承不起作用。類`D <int>`是仍然繼承自類`B <int>`,編譯器仍然讓你可以隱式的做is- a轉換(例如,`D<int>*`到 `B <int> *`),動態綁定仍然有效當虛函數被調用時,等等。但有一個如何查找名稱的問題。 替代方案: * 改變的`f()`的調用為`this->f()`。由于在模板中`this`指針一直是隱式實現的,`this->f()`要依賴查找,因此推遲到模板實例化時,此時所有基類都會被查找。 * 在調用`f()`之前,插入 `using B<T>::f;`語句。 * 改變的`f()`的調用為`B <T>::f()`。 但是請注意,如果`f()`是虛函數,這可能沒有給你想要的東西,因為它禁止了虛函數帶調用機制。 ## 35.20 前一個問題可以暗傷我?難道編譯器默認地產生錯誤代碼?? 是。 由于non-dependent類型 and non-dependent成員不會在dependent模板在基礎類中搜索,編譯器將搜索封閉范圍,比如封閉名字空間。這可能會導致它在你沒有意識到的情況下(!)做錯誤的事情。 例如: ``` ?class?Xyz?{?...?};??←?global?("namespace?scope")?type ?void?f()?{?}????????←?global?("namespace?scope")?function ?template<typename?T> ?class?B?{ ?public: ???class?Xyz?{?...?};??←?type?nested?in?class?B<T> ???void?f()?{?}????????←?member?of?class?B<T> ?}; ?template<typename?T> ?class?D?:?public?B<T>?{ ?public: ???void?g() ???{ ?????Xyz?x;??←?suprise:?you?get?the?global?Xyz!! ?????f();????←?suprise:?you?get?the?global?f!! ???} ?}; ``` `D<T>::g()`內的`Xyz`和`f`將被解析為全局變量,而不是繼承自類`B <T>`,這恐怕不是你的真正意圖。 別埋怨我沒有警告過你。
                  <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>

                              哎呀哎呀视频在线观看