1. 程式人生 > >c++11-17 模板核心知識(二)—— 類模板

c++11-17 模板核心知識(二)—— 類模板

- [類模板宣告、實現與使用](#類模板宣告實現與使用) - [Class Instantiation](#class-instantiation) - [使用類模板的部分成員函式](#使用類模板的部分成員函式) - [Concept](#concept) - [友元](#友元) - [方式一](#方式一) - [方式二](#方式二) - [類模板的全特化](#類模板的全特化) - [類模板的偏特化](#類模板的偏特化) - [多模板引數的偏特化](#多模板引數的偏特化) - [預設模板引數](#預設模板引數) - [Type Aliases](#type-aliases) - [new name for complete type](#new-name-for-complete-type) - [alias template](#alias-template) - [Alias Templates for Member Types](#alias-templates-for-member-types) - [關鍵字typename](#關鍵字typename) - [Using or Typedef](#using-or-typedef) - [類模板的引數推導 Class Template Argument Deduction](#類模板的引數推導-class-template-argument-deduction) - [Deduction Guides](#deduction-guides) ## 類模板宣告、實現與使用 宣告: ```c++ template class Stack { private: std::vector elems; // elements public: void push(T const &elem); // push element void pop(); // pop element T const &top() const; // return top element bool empty() const { // return whether the stack is empty return elems.empty(); } }; ``` 實現: ```c++ template void Stack::push(T const &elem) { elems.push_back(elem); // append copy of passed elem } template void Stack::pop() { assert(!elems.empty()); elems.pop_back(); // remove last element } template T const &Stack::top() const { assert(!elems.empty()); return elems.back(); // return copy of last element } ``` 使用: ```c++ int main() { Stack intStack; // stack of ints Stack stringStack; // stack of strings // manipulate int stack intStack.push(7); std::cout << intStack.top() << '\n'; // manipulate string stack stringStack.push("hello"); std::cout << stringStack.top() << '\n'; stringStack.pop(); } ``` 有兩點需要注意 * 在類宣告內的建構函式、拷貝建構函式、解構函式、賦值等用到類名字的地方,可以將`Stack`簡寫為`Stack`,例如: ```c++ template class Stack { ... Stack (Stack const&); // copy constructor Stack& operator= (Stack const&); // assignment operator ... }; ``` 但是在類外,還是需要`Stack`: ```c++ template bool operator== (Stack const& lhs, Stack const& rhs); ``` * 不可以將類模板宣告或定義在函式或者塊作用域內。通常類模板只能定義在global/namespace 作用域,或者是其它類的聲明裡面。 ## Class Instantiation instantiation的概念在函式模板中說過。在類模板中,類模板函式只有在被呼叫時才會被`instantiate`。在上面的例子中,`push()`和`top()`都會被`Stack`和`Stack`所`instantiate`,但是`pop()`只被`Stack`所`instantiate`。 ![image](https://user-images.githubusercontent.com/14103319/98324443-392d1000-2027-11eb-81d6-c7cce206fcea.png) ## 使用類模板的部分成員函式 我們為Stack新提供`printOn()`函式,這需要`elem`支援`<<`操作: ```c++ template class Stack { ... void printOn() (std::ostream& strm) const { for (T const& elem : elems) { strm << elem << ' '; // call << for each element } } }; ``` 根據上一小節關於類模板的`instantiation`,只有使用到該函式時才會進行該函式的`instantiation`。假如我們的模板引數是元素不支援`<<`的`std::pair< int, int>`,那麼仍然可以使用類模板的其他函式,只有呼叫`printOn`的時候才會報錯: ```c++ Stack> ps; // note: std::pair<> has no operator<< defined ps.push({4, 5}); // OK ps.push({6, 7}); // OK std::cout << ps.top().first << ’\n’; // OK std::cout << ps.top().second << ’\n’; // OK ps.printOn(std::cout); // ERROR: operator<< not supported for element type ``` ### Concept 這就引出了一個問題,我們如何知道一個類模板和它的模板函式需要哪些操作? 在c++11中,我們有`static_assert`: ```c++ template class C { static_assert(std::is_default_constructible::value, "Class C requires default-constructible elements"); ... }; ``` 假如沒有static_assert,提供的模板引數不滿足`std::is_default_constructible`,程式碼也編譯不過。但是編譯器產出的錯誤資訊會很長,包含整個模板`instantiation`的資訊——從開始`instantiation`直到引發錯誤的地方,讓人很難找出錯誤的真正原因。 所以使用static_assert是一個辦法。但是static_assert適用於做簡單的判斷,實際場景中我們的場景會更加複雜,例如判斷模板引數是否具有某個特定的成員函式,或者要求它們支援互相比較,這種情況下使用concept就比較合適。 concept是c++20中用來表明模板庫限制條件的一個特性,在後面會單獨說明concept,這裡為了文章篇幅先暫時只說一下為什麼要有concept. ## 友元 **首先需要明確一點:友元雖然看起來好像是該類的一個成員,但是友元不屬於這個類。這裡友元指的是友元函式和友元類。這點對於理解下面各種語法規則至關重要。** ### 方式一 ```c++ template class Stack { ... void printOn(std::ostream &strm) const { for (T const &elem : elems) { strm << elem << ' '; // call << for each element } } template friend std::ostream &operator<<(std::ostream &, Stack const &); }; template std::ostream &operator<<(std::ostream &strm, Stack const &s) { s.printOn(strm); return strm; } int main() { Stack s; s.push("hello"); s.push("world"); std::cout << s; return 0; } ``` 這裡在類裡宣告的友元函式使用的是與類模板不同的模板引數`