C++中模板的使用
模板(Template)指C++程序設計語言中的函數模板與類模板,是一種參數化類型機制。模板是C++泛型編程中不可缺少的一部分。
C++ templates enable you to define a family of functions or classes that can operate on different types of information.
模板就是實現代碼重用機制的一種工具,它可以實現類型參數化,即把類型定義為參數,從而實現了真正的代碼可重用性。
模板的聲明與定義:模板定義以關鍵字template開始,后接模板形參表(template parameter list),模板形參表是用尖括號括住的一個或者多個模板形參的列表,形參之間以逗號分隔。模板形參可以是表示類型的類型形參(type parameter),也可以是表示常量表達式的非類型形參(non-type parameter)。非類型形參跟在類型說明符之后聲明。類型形參跟在關鍵字class或typename之后聲明。模板形參可以給出默認值(default arguments for template parameters)。
模板的非類型形參(template non-type parameter)允許為以下形式:(1)、整型或枚舉型;(2)、到對象的指針或函數指針;(3)、到對象的引用或函數引用;(4)、成員指針。
模板的非類型參數被聲明為數組或函數的,將被轉換為指針或函數指針。模板的非類型形參允許用const或volatile限定(而模板的類型形參是不允許用const或volatile限定的)。模板的非類型形參是不允許聲明為浮點型、class類型、void型。
模板的模板參數:類模板的模板參數允許是另外一個類模板,這稱為模板的模板參數(template template parameter),也譯作“模板參數模板”。函數模板不允許有模板的模板參數。
模板參數的默認值:模板形參可以給出默認值(default arguments for template parameters)。如果一個模板參數給出了默認值,那么模板形參列表中在其后聲明的模板參數都應該給出默認值。
一個模板的各次聲明給出的模板參數的默認值可以累積其效果。模板參數的作用域為從其聲明之處至該模板的定義結束之處。因此可以使用一個模板參數作為其后聲明的其他模板參數的一部分或默認值。
模板的使用:使用模板時,可以在模板名字后面顯式給出用尖括號括住的模板實參列表(template argument list)。對模板函數或類的模板成員函數,也可不顯式給出模板實參,而是由編譯器根據函數調用的上下文推導出模板實參,這稱為模板參數推導。
如果模板參數使用其默認值,則在模板實參列表中可以忽略它。如果所有的模板參數都使用了默認值,模板實參列表為空,但仍然必須寫出成對的尖括號。
對于作為類型的模板實參,不允許是局部類型(local type)、無鏈接性的類型(type with no linkage)、無名類型(unnamed type)或包括了這三種情形的復合類型。但C++11以允許本地類型作為模板實參。
模板的嵌套:成員模板,對于類中的模板成員函數、嵌套的成員類模板,可以在封閉類的內部或外部定義它們。當模板成員函數、嵌套類模板在其封閉類的外部定義時,必須以封閉類模板的模板參數(如果它們也是模板類)和成員模板的模板參數開頭。C++標準規定:如果外圍的類模板沒有特例化,里面的成員模板就不能特例化。
依賴名字與typename關鍵字:一個模板中的依賴于一個模板參數(template parameter)的名字被稱為依賴名字(dependent name)。當一個依賴名字嵌套在一個類的內部時,稱為嵌套依賴名字(nested dependent name)。一個不依賴于任何模板參數的名字,稱為非依賴名字(non-dependent name)。
編譯器在處理模板定義時,可能并不確定依賴名字表示一個類型,還是嵌套類的成員,還是類的靜態成員。C++標準規定:如果解析器在一個模板中遇到一個嵌套依賴名字,它假定那個名字不是一個類型,除非顯式用typename關鍵字前置修飾該名字。
typename關鍵字有兩個用途:(1)、常見的在模板定義中的模板形參列表,表示一個模板參數是類型參數。等同于使用class。(2)、使用模板類內定義的嵌套依賴類型名字時,顯式指明這個名字是一個類型名。否則,這個名字會被理解為模板類的靜態成員名。C++11起,這一用途也可以出現在模板以外,盡管此時typename關鍵字不是必要的。
在下述情形,對嵌套依賴類型名字不需要前置修飾typename關鍵字:(1)、派生類聲明的基類列表中的基類標識符;(2)、成員初始化列表中的基類標識符;(3)、用class、struct、enum等關鍵字開始的類型標識符。因為它們的上下文已經指出這些標識符就是作為類型的名字。
template關鍵字有兩個用途:(1)、常見的在模板定義的開始;(2)、模板類內部定義了模板成員函數或者嵌套的成員模板類。在模板中,當引用這樣的模板成員函數或嵌套的成員模板類時,可以在::(作用域解析)運算符、.(以對象方式訪問成員)運算符、->(以指針方式訪問成員)運算符之后使用template關鍵字,隨后才是模板成員函數名字或嵌套的成員模板類名字,這使得隨后的左尖括號<被解釋為模板參數列表的開始,而不是小于號運算符。C++11起,這一用途也可以出現在模板以外,盡管此時template關鍵字不是必要的。
???????? 模板實例化(template instantiation):是指在編譯或鏈接時生成函數模板或類模板的具體實例源代碼。ISO C++定義了兩種模板實例化方法:隱式實例化(當使用實例化的模板時自動地在當前代碼單元之前插入模板的實例化代碼)、顯式實例化(直接聲明模板實例化)。在C++語言的不同實現中,模板編譯模式(模板初始化的方法)大致可分為三種:
(1)、Borland模型(包含模板編譯模式):編譯器生成每個編譯單元中遇到的所有的模板實例,并存放在相應的目標文件中;鏈接器合并相同的模板實例,生成可執行文件。為了在每次模板實例化時模板的定義都是可見的,模板的聲明與定義放在同一個.h文件中。這種方法的優點是鏈接器只需要處理目標文件;這種方法的缺點是由于模板實例被重復編譯,編譯時間被加長了,而且不能使用系統的鏈接器,需重新設計鏈接器。
(2)、Cfront/查詢模型(分離(Separation)模板編譯模式):AT&T公司的C++編譯器Cfront為解決模板實例化問題,增加了一個模板倉庫,用以存放模板實例的代碼并可被自動維護。當生成一個目標文件時,編譯器把遇到的模板定義與當前可生成的模板實例存放到模板倉庫中。鏈接時,鏈接器的包裝程序(wrapper)首先調用編譯器生成所有需要的且不在模板倉庫中的模板實例。這種方法的優點是編譯速度得到了優化,而且可以直接使用系統的鏈接器;這種方法的缺點是復雜度大大增加,更容易出錯。使用這種模型的源程序通常把模板聲明與非內聯的模板成員分別放在.h文件與模板定義文件中,后者單獨編譯。
(3)、混合(迭代)模型:g++目前是基于Borland模型完成模板實例化。g++未來將實現混合模型的模板實例化,即編譯器把編譯單元中的模板定義與遇到的當前可實現的模板實例存放在相應的目標文件中;鏈接器的包裝程序(wrapper)調用編譯器生成所需的目前還沒有實例化的模板實例;鏈接器合并所有相同的模板實例。使用這種模型的源程序通常把模板聲明與非內聯的模板成員分別放在.h文件與模板定義文件中,后者單獨編譯。
ISO C++標準規定,如果隱式實例化模板,則模板的成員函數一直到引用時才被實例化;如果顯式實例化模板,則模板所有成員立即都被實例化,所以模板的聲明與定義在此處都應該是可見的,而且在其它程序文本文件使用了這個模板實例時用編譯器選項抑制模板隱式實例化,或者模板的定義部分是不可見的,或者使用template<> type FUN_NAME(type list)的語句聲明模板的特化但不實例化。
關于模板實例化(template instantiation):
(1)、指在編譯或鏈接時生成函數模板或類模板的具體實例源代碼,即用使用模板時的實參類型替換模板類型參數(還有非類型參數和模板型參數);
(2)、隱式實例化(implicit instantiation):當使用實例化的模板時自動地在當前代碼單元之前插入模板的實例化代碼,模板的成員函數一直到引用時才被實例化;
(3)、顯式實例化(explicit instantiation):直接聲明模板實例化,模板所有成員立即都被實例化;
(4)、實例化也是一種特例化,被稱為實例化的特例(instantiated(or generated) specialization)。
隱式實例化時,成員只有被引用到才會進行實例化,這被稱為推遲實例化(lazy instantiation)。
模板實例化是生成采用特定模板參數組合的具體類或函數(實例)。例如,編譯器生成一個采用Array<int>的類,另外生成一個采用Array<double>的類。通過用模板參數替換模板類定義中的模板參數,可以定義這些新的類。
關于模板的編譯和鏈接:
(1)、包含模板編譯模式:編譯器生成每個編譯單元中遇到的所有的模板實例,并存放在相應的目標文件中;鏈接器合并等價的模板實例,生成可執行文件,要求實例化時模板定義可見,不能使用系統鏈接器;
(2)、分離模板編譯模式(使用export關鍵字):不重復生成模板實例,編譯器設計要求高,可以使用系統鏈接器;
(3)、包含編譯模式是主流,C++11已經棄用export關鍵字(對模板引入extern新用法),一般將模板的全部實現代碼放在同一個頭文件中并在用到模板的地方用#include包含頭文件,以防止出現實例不一致。
關于template、typename、this關鍵字的使用:
(1)、依賴于模板參數(template parameter,形式參數,實參英文為argument)的名字被稱為依賴名字(dependent name),C++標準規定,如果解析器在一個模板中遇到一個嵌套依賴名字,它假定那個名字不是一個類型,除非顯式用typename關鍵字前置修飾該名字;
(2)、和上一條typename用法類似,template用于指明嵌套類型或函數為模板;
(3)、this用于指定查找基類中的成員(當基類是依賴模板參數的類模板實例時,由于實例化總是推遲,這時不依賴模板參數的名字不在基類中查找)。
C++11 關于模板的新特性:
(1)、”>>”根據上下文自動識別正確語義;
(2)、函數模板參數默認值;
(3)、變長模板參數(擴展sizeof…()獲取參數個數);
(4)、模板別名(擴展using關鍵字);
(5)、外部模板實例(拓展extern關鍵字),棄用export template。
函數模板:模板(Templates)使得我們可以生成通用的函數,這些函數能夠接受任意數據類型的參數,可返回任意類型的值,而不需要對所有可能的數據類型進行函數重載。這在一定程度上實現了宏(macro)的作用。模板函數也可以提前聲明,不過聲明時需要帶上模板頭。
函數模板描述了僅用參數或返回值的類型來區分的一組相關函數。對于函數模板,編譯器不支持函數參數列表中非類型模板參數的表達式。
類模板(class templates):使得一個類可以有基于通用類型的成員,而不需要在類生成的時候定義具體的數據類型
模板特殊化(特例化,Templatespecialization):由以下格式定義:
template<> class class_name <type>
這個特殊化本身也是模板定義的一部分,因此,我們必須在該定義開頭寫template <>。而且因為它確實為一個具體類型的特殊定義,通用數據類型在這里不能夠使用,所以第一對尖括號<>內必須為空。在類名稱后面,我們必須將這個特殊化中使用的具體數據類型寫在尖括號<>中。
當我們特殊化模板的一個數據類型的時候,同時還必須重新定義類的所有成員的特殊化實現。這樣做的原因就是特殊化不會繼承通用模板的任何一個成員。
所謂模板特例化即對于通例中的某種或某些情況做單獨專門實現,最簡單的情況是對每個模板參數指定一個具體值,這成為完全特例化(full specialization),另外,可以限制模板參數在一個范圍取值或滿足一定關系等,這稱為部分特例化(partial specialization),用數學上集合的概念,通例模板參數所有可取的值組合構成全集U,完全特例化對U中某個元素進行專門定義,部分特例化對U的某個真子集進行專門定義。
關于模板特例化:
(1)、在定義模板特例之前必須已經有模板通例(primary template)的聲明;
(2)、模板特例并不要求一定與通例有相同的接口,但為了方便使用(體會特例的語義)一般都相同;
(3)、匹配規則,在模板實例化時如果有模板通例、特例加起來多個模板版本可以匹配,則依據如下規則:對版本AB,如果A的模板參數取值集合是B的真子集,則優先匹配A,如果AB的模板參數取值集合是”交叉”關系(AB)交集不為空,且不為包含關系),則發生編譯錯誤,對于函數模板,用函數重載分辨(overload resolution)規則和上述規則結合并優先匹配非模板函數。
從編譯器的角度來看,模板不同于一般的函數或類。它們在需要時才被編譯(compiled on demand),也就是說一個模板的代碼直到需要生成一個對象的時候(instantiation)才被編譯。當需要instantiation的時候,編譯器根據模板為特定的調用數據類型生成一個特殊的函數。
測試代碼如下:
template.hpp:
#ifndef FBC_MESSY_TEST_TEMPLATE_HPP_
#define FBC_MESSY_TEST_TEMPLATE_HPP_#include <vector>// reference: https://zh.wikipedia.org/wiki/%E6%A8%A1%E6%9D%BF_(C%2B%2B)
// 函數模板,此函數在編譯時會自動產生對應參數類型的代碼,而不用顯式聲明
template <typename T>
inline const T& maximum(const T& x, const T& y)
{if (y > x){return y;} else{return x;}
}// 類模板
template <typename Ty>
class ComPtr
{
protected:Ty* m_ptr;
public:ComPtr() {m_ptr = NULL;}ComPtr(const ComPtr& rhs) {m_ptr = NULL;SetComPtr(rhs.m_ptr);}ComPtr(Ty* p) {m_ptr = NULL;SetComPtr(p);}~ComPtr() {Release();}const ComPtr& operator=(const ComPtr& rhs) {SetComPtr(rhs.m_ptr);return *this;}Ty* operator=(Ty* p) {SetComPtr(p);return p;}operator Ty* () {return m_ptr;}Ty* operator->() {return m_ptr;}operator Ty** () {Release();return &m_ptr;}operator void** () {Release();return (void**)&m_ptr;}bool IsEmpty() {return (m_ptr == NULL);}void SetComPtr(Ty* p) {Release();m_ptr = p;if (m_ptr) {m_ptr->AddRef();}}void Release() {if (m_ptr) {m_ptr->Release();m_ptr = NULL;}}
};// 模板的嵌套:成員模板
template <typename C> class myc{
public:template <typename S> C foo(S s);
};//下行需要給出外部類與內部嵌套類的模板形參列表:
template<typename C> template <typename S> C myc<C>::foo(S s){C var;return var;
}// reference: http://www.tutorialspoint.com/cplusplus/cpp_templates.htm
// function template
template <typename T>
inline T const& Max(T const& a, T const& b)
{return a < b ? b : a;
}// class template
template <class T>
class Stack {
private:std::vector<T> elems; // elements public:void push(T const&); // push element void pop(); // pop element T top() const; // return top element bool empty() const{ // return true if empty.return elems.empty();}
};template <class T>
void Stack<T>::push(T const& elem)
{// append copy of passed element elems.push_back(elem);
}template <class T>
void Stack<T>::pop()
{if (elems.empty()) {throw fprintf(stderr, "Stack<>::pop(): empty stack\n");}// remove last element elems.pop_back();
}template <class T>
T Stack<T>::top() const
{if (elems.empty()) {throw fprintf(stderr, "Stack<>::top(): empty stack\n");}// return copy of last element return elems.back();
}// reference: http://www.prglab.com/cms/pages/c-tutorial/advanced-concepts/templates.php
// 模板特殊化(特例化,Template specialization)
template <class T> class pair {
private:T value1, value2;
public:pair(T first, T second){value1 = first;value2 = second;}T module() { return 0; }
};template <>
class pair <int> {
private:int value1, value2;
public:pair(int first, int second){value1 = first;value2 = second;}int module() { return value1 % value2; }
};template <class T> struct PrintType {};template<> struct PrintType <int> {const std::string type(){return "int type";}
};template<> struct PrintType <float> {const std::string type(){return "float type";}
};template<> struct PrintType <std::string> {const std::string type(){return "string type";}
};// 函數模板聲明與定義分開
template <typename T>
inline const T& max_mum(const T& x, const T& y);// 類模板聲明與定義分開
template<class T>
class A {
public:void f();
};void test_template1();
void test_template2();
void test_template3();
void test_template4();
void test_template5();
void test_template6();
void test_template7();#endif // FBC_MESSY_TEST_TEMPLATE_HPP_
template_impl.cpp:
#include <iostream>
#include "template.hpp"template <typename T>
const T& max_mum(const T& x, const T& y)
{if (y > x){return y;}else{return x;}
}// 函數模板聲明與定義分開:函數模板顯示實例化
template const int& max_mum<int>(const int&, const int&);template<class T>
void A<T>::f()
{std::cout << " template class impl" << std::endl;
}// 類模板聲明與定義分開:類模板顯示實例化
template class A < int > ;
template.cpp:
#include <iostream>
#include <string>
#include <stdexcept>
#include "template.hpp"void test_template1()
{int a = 3, b = 7;float x = 3.0, y = 7.0;//Calling template functionstd::cout << maximum<int>(a, b) << std::endl; //輸出 7std::cout << maximum(a, b) << std::endl; //自動補充類型聲明,輸出 7std::cout << maximum<double>(x, y) << std::endl; //輸出 7
}void test_template2()
{int i = 39;int j = 20;std::cout << "Max(i, j): " << Max(i, j) << std::endl;double f1 = 13.5;double f2 = 20.7;std::cout << "Max(f1, f2): " << Max(f1, f2) << std::endl;std::string s1 = "Hello";std::string s2 = "World";std::cout << "Max(s1, s2): " << Max(s1, s2) << std::endl;
}void test_template3()
{try {Stack<int> intStack; // stack of ints Stack<std::string> stringStack; // stack of strings // manipulate int stack intStack.push(7);std::cout << intStack.top() << std::endl;// manipulate string stack stringStack.push("hello");std::cout << stringStack.top() << std::endl;stringStack.pop();//stringStack.pop();} catch (std::exception const& ex) {std::cerr << "Exception: " << ex.what() << std::endl;return;}
}void test_template4()
{pair <int> myints(100, 75);pair <float> myfloats(100.0, 75.0);std::cout << myints.module() << std::endl;std::cout << myfloats.module() << std::endl;
}void test_template5()
{PrintType<int> type_int;std::cout << type_int.type()<<std::endl;PrintType<float> type_float;std::cout << type_float.type() << std::endl;PrintType<std::string> type_string;std::cout << type_string.type() << std::endl;
}void test_template6()
{int ret = 0, a = 5, b = 7;ret = max_mum(a, b);std::cout << ret << std::endl;
}void test_template7()
{A<int> a;a.f();
}
主要參考文獻:
1.?https://zh.wikipedia.org/wiki/%E6%A8%A1%E6%9D%BF_(C%2B%2B)
2.?http://blog.jobbole.com/83461/
3.?https://docs.oracle.com/cd/E19205-01/820-1214/bkaew/index.html
GitHub:https://github.com/fengbingchun/Messy_Test
總結
- 上一篇: C++中istream的使用
- 下一篇: json11库的使用