Part III: Tools for Class Authors
Chapter 16. Templates and Generic Programming
OOP 与泛型编程都可以处理在编写程序时不知道类型的情况。区别是:OOP 处理的类型直到运行时才知道,泛型编程在编译时就知道类型了。
模板是C++泛型编程的基础。
16.1 定义模板
// returns 0 if the values are equal, -1 if v1 is smaller, 1 if v2 is smaller
int compare(const string &v1, const string &v2) {
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
int compare(const double &v1, const double &v2) {
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
上面两个函数几乎相同,唯一的区别就是形参的类型不同。
函数模板
可以定义一个函数模板 (function template),而不是为每个类型定义一个新函数。
函数模板是一个公式,通过该公式可以生成该函数的特定于类型的版本。
compare 的模板版本看起来如下:
template <typename T>
int compare(const T &v1, const T &v2) {
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
模板定义以关键字 template
开始,后面跟着模板形参列表 (template parameter list),这是以逗号分隔的一个或多个模板形参 (template parameter),用尖括号包围起来。
注:在模板定义中,模板形参列表不能为空。
模板形参表示类或函数定义用到的类型或值。当使用模板时,隐式地或显式地指定模板实参 (template argument),将其绑定到模板形参上。
实例化函数模板
当调用函数模板时,编译器(一般)使用调用的实参来推断模板的实参。
cout << compare(1, 0) << endl; // T is int
编译器使用推断的模板形参来实例化 (instantiate) 特定版本的函数。当编译器实例化模板时,它使用实际的模板实参替代对应的模板形参来创建一个新的“实例”。
// instantiates int compare(const int&, const int&)
cout << compare(1, 0) << endl; // T is int
// instantiates int compare(const vector<int>&, const vector<int>&)
vector<int> vec1{1, 2, 3}, vec2{4, 5, 6};
cout << compare(vec1, vec2) << endl; // T is vector<int>
编译器实例化两个不同版本的 compare。对于第一个调用,编译器编写并编译一个 compare 版本,将 T 替换成 int。
int compare(const int &v1, const int &v2) {
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
对于第二个调用,它会生成另一个 compare 版本,将 T 替换成 vector<int>。这些编译器生成的函数一般被称为模板的实例 (instantiation)。
模板类型形参
上面的 compare 函数有一个模板类型形参 (type parameter)。可以将类型形参作为类型说明符使用,像使用内置或类类型说明符一样。特别地,类型形参可以用来命名返回类型或函数形参类型,以及用于函数体内的变量声明或强制转换。
// ok: same type used for the return type and parameter
template <typename T> T foo(T* p) {
T tmp = *p; // tmp will have the type to which p points
// ...
return tmp;
}
每个类型形参前面必须有关键字 class
或 typename
。
// error: must precede U with either typename or class
template <typename T, U> T calc(const T&, const U&);
这两个关键字含义相同,在模板形参列表中可以互换使用。模板形参列表还可以同时使用这两个关键字:
// ok: no distinction between typename and class in a template parameter list
template <typename T, class U> calc (const T&, const U&);
非类型模板形参
除了定义类型形参之外,还可以定义接受非类型形参 (nontype parameter) 的模板。一个非类型形参是一个值而不是类型。通过使用特定的类型名来指定非类型形参。
当模板被实例化时,非类型形参被替换成用户提供或编译器推断的值。
template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M]) {
return strcmp(p1, p2);
}
当调用上面版本的 compare 时,
compare("hi", "mom")
编译器将使用字面值常量的大小替代 N 和 M 来实例化模板。因为编译器会在字面值常量的末尾插入一个空字符,故编译器实例化
int compare(const char (&p1)[3], const char (&p2)[4])
非类型形参可以是整型,或是指向对象或函数类型的指针或(左值)引用。
- 绑定到非类型整数形参的实参必须是常量表达式。
- 绑定到指针或引用非类型形参的实参必须有静态生存期。
不能使用非static局部变量或动态对象作为引用或指针非类型非类型模板形参的实参。
指针形参可以用 nullptr 或值为 0 的常量表达式。
在c++中,静态生存期与程序的运行期相同。全局变量、静态全局变量、静态局部变量都具有静态生存期。
注:非类型模板形参的实参必须是常量表达式。
inline 和 constexpr 函数模板
函数模板可以被声明为 inline 和 constexpr。inline 和 constexpr 说明符放在模板形参列表后面、返回类型之前。
// ok: inline specifier follows the template parameter list
template <typename T> inline T min(const T&, const T&);
// error: incorrect placement of the inline specifier
inline template <typename T> T min(const T&, const T&);
编写与类型无关的代码
编写泛型代码的两个重要原则:
- 模板中的函数形参是 const 引用。
- 函数体中的测试只使用 < 比较。
实际上,如果真正关心类型独立性和可移植性,则很可能应该使用 less 定义函数:
// version of compare that will be correct even if used on pointers; see § 14.8.2
template <typename T> int compare(const T &v1, const T &v2) {
if (less<T>()(v1, v2)) return -1;
if (less<T>()(v2, v1)) return 1;
return 0;
}
原始版本的问题是:如果用户调用它比较两个指向不同数组的指针,那么代码行为是未定义的。
实践:模板程序应尽量减少对实参类型的要求。
模板编译
当编译器看见模板的定义时,它不会产生代码。只有当实例化模板的一个特定版本时才会产生代码。这一事实影响了组织源代码的方式与错误检测的时间。
为了生成实例,编译器需要定义函数模板或类模板成员函数的代码。因此,与非模板代码不同,模板的头文件包括声明与定义。
编译错误大多在实例化期间报告
通常,编译器可能会在三个阶段标记错误:
- 第一个阶段是编译模板本身时。编译器可以检测到语法错误,比如忘记分号或变量名拼写错误等。
- 第二个错误检测时间是当编译器看到模板使用时。对于函数模板的调用,编译器一般检查实参的数目是否正确;还可以检查支持两个相同类型的两个实参类型是否相同。对于类模板,编译器可以检查模板实参的正确数目。
- 第三个阶段是实例化期间。只有这个阶段类型相关的错误才能被找到。取决于编译器管理实例化的方式,这些错误可能在链接时报告。
当编写模板时,代码不是特定于类型的,但是模板代码通常会对要使用的类型做出一些假设。
if (v1 < v2) return -1; // requires < on objects of type T
if (v2 < v1) return 1; // requires < on objects of type T
return 0; // returns int; not dependent on T
如果传递给 compare 实参有 < 操作,那么代码是正确的,否则是错误的:
Sales_data data1, data2;
cout << compare(data1, data2) << endl; // error: no < on Sales_data
警告:调用者有责任保证传递给模板的实参支持模板使用的任何操作,并且在模板使用它们的代码中,这些操作行为正确。
类模板
类模板 (class template) 是用于生成类的蓝图。与函数模板不同的是,编译器不能推断类模板的模板形参类型。为了使用类模板,必须在尖括号内提供额外信息。额外信息是模板实参列表,替代模板形参。
定义类模板
作为一个例子,实现 StrBlob (§ 12.1) 的模板版本。
template <typename T> class Blob {
public:
typedef T value_type;
typedef typename std::vector<T>::size_type size_type;
// constructors
Blob();
Blob(std::initializer_list<T> il);
// number of elements in the Blob
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
// add and remove elements
void push_back(const T &t) {data->push_back(t);}
// move version; see § 13.6.3
void push_back(T &&t) { data->push_back(std::move(t)); }
void pop_back();
// element access
T& back();
T& operator[](size_type i); // defined in § 14.5
private:
std::shared_ptr<std::vector<T>> data;
// throws msg if data[i] isn't valid
void check(size_type i, const std::string &msg) const;
};
实例化类模板
当使用类模板时,必须提供额外信息。额外信息是显式模板实参 (explicit template argument) 的列表,绑定到模板的形参。编译器使用这些模板实参从模板中实例化特定的类。
Blob<int> ia; // empty Blob<int>
Blob<int> ia2 = {0,1,2,3,4}; // Blob<int> with five elements
从上面两个定义,编译器实例化一个类等价于
template <> class Blob<int> {
typedef typename std::vector<int>::size_type size_type;
Blob();
Blob(std::initializer_list<int> il);
// ...
int& operator[](size_type i);
private:
std::shared_ptr<std::vector<int>> data;
void check(size_type i, const std::string &msg) const;
};
当编译器从 Blob 模板中实例化一个类时,它重写 Blob 模板,将模板形参 T 的每个实例替换成给定模板实参,在本例中是 int。
注:类模板的每个实例都构成一个独立的类。Blob<int> 类型与任何其他 Blob 类型的成员没有关系,也没有访问特权。
在模板作用域内对模板类型的引用
std::shared_ptr<std::vector<T>> data;
类模板的成员函数
与其他类相同,可以在类主体的内部或外部定义类模板的成员函数,定义在类主体内部的成员是隐式内联的。
类模板成员函数本身是一个普通函数。然而,类模板的每个实例拥有它自己版本的成员。因此,类模板的成员函数具有与类本身相同的模板形参。所以,定义在类模板外部的成员函数以关键字 template
与类模板形参列表开始。
StrBlob 成员函数定义为
ret-type StrBlob::member-name(parm-list)
对应 Blob 成员定义为
template <typename T>
ret-type Blob<T>::member-name(parm-list)
check 与元素访问成员
template <typename T>
void Blob<T>::check(size_type i, const std::string &msg) const {
if (i >= data->size())
throw std::out_of_range(msg);
}
template <typename T>
T& Blob<T>::back() {
check(0, "back on empty Blob");
return data->back();
}
template <typename T>
T& Blob<T>::operator[](size_type i) {
// if i is too big, check will throw, preventing access to a nonexistent element
check(i, "subscript out of range");
return (*data)[i];
}
template <typename T>
void Blob<T>::pop_back(){
check(0, "pop_back on empty Blob");
data->pop_back();
}
Blob 构造函数
template <typename T>
Blob<T>::Blob(): data(std::make_shared<std::vector<T>>()) { }
template <typename T>
Blob<T>::Blob(std::initializer_list<T> il): data(std::make_shared<std::vector<T>>(il)) { }
类模板成员函数的实例化
默认情况下,类模板成员函数只有当程序使用它时才实例化。这一事实允许我们使用不满足一些模板操作要求的类型来实例化一个类。
// instantiates Blob<int> and the initializer_list<int> constructor
Blob<int> squares = {0,1,2,3,4,5,6,7,8,9};
// instantiates Blob<int>::size() const
for (size_t i = 0; i != squares.size(); ++i)
squares[i] = i*i; // instantiates Blob<int>::operator[](size_t)
在类代码内简化模板类名的使用
当使用模板类型时必须提供模板实参这一规则有一个例外:在类模板本身作用域内,可以使用模板名不带实参。
// BlobPtr throws an exception on attempts to access a nonexistent element
template <typename T> class BlobPtr
public:
BlobPtr(): curr(0) { }
BlobPtr(Blob<T> &a, size_t sz = 0): wptr(a.data), curr(sz) { }
T& operator*() const {
auto p = check(curr, "dereference past end");
return (*p)[curr]; // (*p) is the vector to which this object points
}
// increment and decrement
BlobPtr& operator++(); // prefix operators
BlobPtr& operator--();
private:
// check returns a shared_ptr to the vector if the check succeeds
std::shared_ptr<std::vector<T>> check(std::size_t, const std::string&) const;
// store a weak_ptr, which means the underlying vector might be destroyed
std::weak_ptr<std::vector<T>> wptr;
std::size_t curr; // current position within the array
};
上面代码中,BlobPtr 的前置递增和递减成员返回 BlobPtr&,而不是 BlobPtr<T>&。
在类模板的作用域内,编译器将对模板本身的引用视为已提供了与模板自己形参匹配的模板实参。即,如同写了下面的代码:
BlobPtr<T>& operator++();
BlobPtr<T>& operator--();
在类模板体外使用类模板名
在类模板体外定义成员时,并不在类作用域中,直到类名可见。
// postfix: increment/decrement the object but return the unchanged value
template <typename T>
BlobPtr<T> BlobPtr<T>::operator++(int) {
// no check needed here; the call to prefix increment will do the check
BlobPtr ret = *this; // save the current value
++*this; // advance one element; prefix ++ checks the increment
return ret; // return the saved state
}
在函数体内,已经在类作用域中,所以在定义 ret 时不需要重复写模板实参。
ret 的定义如同下面代码:
BlobPtr<T> ret = *this;
注:在类模板作用域内,可以使用模板而不需指定模板实参。
类模板与友元
当一个类包含一个友元声明时,类与友元可以是模板,也可以不是。
如果类模板有一个非模板友元,它将模板的所有实例的权限授予该友元。
当友元本身是模板时,类可以授权给友元模板的所有实例,或只授权给特定实例。
一对一友元关系
从类模板到另一个模板(类或函数)的最常见友元关系形式是,在类及其友元对应实例之间建立友元关系。
例如,Blob 类应将 BlobPtr 类和 Blob 相等运算符的模板版本声明为友元。
为了引用模板(类或函数)的特定实例,必须首先声明模板本身。模板声明包含模板的模板形参列表:
// forward declarations needed for friend declarations in Blob
template <typename> class BlobPtr;
template <typename> class Blob; // needed for parameters in operator==
template <typename T> bool operator==(const Blob<T>&, const Blob<T>&);
template <typename T> class Blob {
// each instantiation of Blob grants access to the version of
// BlobPtr and the equality operator instantiated with the same type
friend class BlobPtr<T>;
friend bool operator==<T> (const Blob<T>&, const Blob<T>&);
// other members as in § 12.1
};
友元声明将 Blob 模板形参作为它们自己的模板实参。因此,友元关系限制在那些使用相同类型实例化的 BlobPtr 和相等运算符的实例。
Blob<char> ca; // BlobPtr<char> and operator==<char> are friends
Blob<int> ia; // BlobPtr<int> and operator==<int> are friends
通用与特定的模板友元关系
一个类还可以使另一个模板的每个实例成为其友元,或者可以将友元关系限制为特定的实例:
// forward declaration necessary to befriend a specific instantiation of a
template template <typename T> class Pal;
class C { // C is an ordinary, nontemplate class
friend class Pal<C>; // Pal instantiated with class C is a friend to C
// all instances of Pal2 are friends to C;
// no forward declaration required when we befriend all instantiations
template <typename T> friend class Pal2;
};
template <typename T> class C2 { // C2 is itself a class template
// each instantiation of C2 has the same instance of Pal as a friend
friend class Pal<T>; // a template declaration for Pal must be in scope
// all instances of Pal2 are friends of each instance of C2, prior declaration needed
template <typename X> friend class Pal2;
// Pal3 is a nontemplate class that is a friend of every instance of C2
friend class Pal3; // prior declaration for Pal3 not needed
};
为了允许所有实例作为友元,友元声明必须使用与类本身不同的模板形参。
令模板自己的类型形参成为友元
在C++11标准下,可以使用模板类型形参成为友元。
template <typename Type> class Bar {
friend Type; // grants access to the type used to instantiate Bar
// ...
};
注意,即使友元一般必须是类或函数,使用内置类型实例化 Bar 也是可以的。
模板类型别名
类模板的一个实例定义了一个类类型,因此,可以定义一个 typedef 表示实例化的类。
typedef Blob<string> StrBlob;
因为模板不是一个类型,所以不能定义一个 typedef 表示模板。即,无法定义一个 typedef 表示 Blob<T>。
但是,C++11标准允许为类模板定义类型别名。
template<typename T> using twin = pair<T, T>;
twin<string> authors; // authors is a pair<string, string>
当定义一个模板类型别名时,可以固定一个或多个模板形参。
template <typename T> using partNo = pair<T, unsigned>;
partNo<string> books; // books is a pair<string, unsigned>
partNo<Vehicle> cars; // cars is a pair<Vehicle, unsigned>
类模板的 static 成员
类模板可以声明 static 成员。
template <typename T> class Foo {
public:
static std::size_t count() { return ctr; }
// other interface members
private:
static std::size_t ctr;
// other implementation members
};
Foo 的每个实例拥有自己的 static 成员实例。即,对于给定类型 X,有一个 Foo<X>::ctr 和一个 Foo<X>::count 成员。
// instantiates static members Foo<string>::ctr and Foo<string>::count
Foo<string> fs;
// all three objects share the same Foo<int>::ctr and Foo<int>::count members
Foo<int> fi, fi2, fi3;
模板类的每个 static 成员必须恰好只有一个定义。但是,类模板的每个实例都有不同的对象。因此,与定义模板的成员函数的方式类似,将 static 数据成员定义成模板。
template <typename T>
size_t Foo<T>::ctr = 0; // define and initialize ctr
可以通过一个类类型的对象访问类模板的 static 成员,或者使用作用域运算符直接访问成员。
Foo<int> fi; // instantiates Foo<int> class
// and the static data member ctr
auto ct = Foo<int>::count(); // instantiates Foo<int>::count
ct = fi.count(); // uses Foo<int>::count
ct = Foo::count(); // error: which template instantiation?
一个 static 成员只有在程序中使用时才会实例化。
模板形参
模板形参名字没有什么内在含义。通常将类型形参命名为 T,实际上可以使用任何名字。
template <typename Foo> Foo calc(const Foo& a, const Foo& b) {
Foo tmp = a; // tmp has the same type as the parameters and return type
// ...
return tmp; // return type and parameters have the same type
}
模板形参与作用域
模板形参的名字可以在它声明后使用,直到模板声明或定义结尾。模板形参会隐藏外层作用域该名字的声明。但是,被用作模板形参的名字不能在模板内被重新使用。
typedef double A;
template <typename A, typename B> void f(A a, B b) {
A tmp = a; // tmp has same type as the template parameter A, not double
double B; // error: redeclares template parameter B
}
因为形参名不能被重新使用,所以在给定模板形参列表中,模板形参的名字只能出现一次。
// error: illegal reuse of template parameter name V
template <typename V, typename V> // ...
模板声明
模板声明必须包括模板形参。
// declares but does not define compare and Blob
template <typename T> int compare(const T&, const T&);
template <typename T> class Blob;
对于同一个模板,定义中模板形参的名字不需要与声明中相同。
// all three uses of calc refer to the same function template
template <typename T> T calc(const T&, const T&); // declaration
template <typename U> U calc(const U&, const U&); // declaration
// definition of the template template
<typename Type> Type calc(const Type& a, const Type& b) { /* . . . */ }
实践:给定文件所需的所有模板的声明通常应出现在文件的开头,在使用这些名称的任何代码之前。
使用类的类型成员
在非模板代码中,编译器可以访问类定义。因此,它知道通过作用域运算符访问的是类型还是 static 成员。
假设 T 是一个模板类型形参,当编译器遇到如 T::mem 这样的代码时,它不知道 mem 是类型还是 static 成员,直到它被实例化。但是,为了处理模板,编译器必须知道一个名字是否代表类型。
默认情况下,C++语言认为通过作用域运算符访问的名字不是类型。因此,如果想要使用模板类型形参的类型成员,必须显式地告诉编译器该名字是类型。这可以通过使用关键字 typename
来完成。
template <typename T> typename T::value_type top(const T& c) {
if (!c.empty())
return c.back();
else
return typename T::value_type();
}
上面的 top 函数期待一个容器作为实参,并使用 typename 指定返回类型;当 c 没有元素时,生成一个值初始化的元素。
注:当想要通知编译器某个名字是代表类型的,必须使用关键字 typename,而不是 class。
默认模板实参
在C++11标准中,可以为函数和类模板提供默认实参。在更早的版本中,只能为类模板提供实参。
// compare has a default template argument, less<T>
// and a default function argument, F()
template <typename T, typename F = less<T>>
int compare(const T &v1, const T &v2, F f = F()) {
if (f(v1, v2)) return -1;
if (f(v2, v1)) return 1;
return 0;
}
bool i = compare(0, 42); // uses less; i is -1
// result depends on the isbns in item1 and item2
Sales_data item1(cin), item2(cin);
bool j = compare(item1, item2, compareIsbn);
与函数默认实参一样,对于某个模板形参,只有当它右侧的所有形参都有默认实参时,该形参才有默认实参。
模板默认实参与类模板
当使用类模板时,必须在模板名之后添加尖括号。尖括号表示这个类必须从模板中实例化。
template <class T = int> class Numbers { // by default T is int
public:
Numbers(T v = 0): val(v) { } // various operations on numbers
private:
T val;
};
Numbers<long double> lots_of_precision;
Numbers<> average_precision; // empty <> says we want the default type
成员模板
类可以包含本身是模板的成员函数。这样的成员被称为成员模板 (member template)。成员模板不能是虚函数。
非模板类的成员模板
// function-object class that calls delete on a given pointer
class DebugDelete {
public:
DebugDelete(std::ostream &s = std::cerr): os(s) { }
// as with any function template, the type of T is deduced by the compiler
template <typename T> void operator()(T *p) const {
os << "deleting unique_ptr" << std::endl;
delete p;
}
private:
std::ostream &os;
};
double* p = new double;
DebugDelete d; // an object that can act like a delete expression
d(p); // calls DebugDelete::operator()(double*), which deletes p
int* ip = new int;
// calls operator()(int*) on a temporary DebugDelete object
DebugDelete()(ip);
因为调用一个 DebugDelete 对象会 delete 它给定的指针,所以可以使用 DebugDelete 作为 unique_ptr 的删除器。
// destroying the the object to which p points
// instantiates DebugDelete::operator()<int>(int *)
unique_ptr<int, DebugDelete> p(new int, DebugDelete());
// destroying the the object to which sp points
// instantiates DebugDelete::operator()<string>(string*)
unique_ptr<string, DebugDelete> sp(new string, DebugDelete());
类模板的成员模板
template <typename T> class Blob {
template <typename It> Blob(It b, It e); // 构造函数
// ...
};
成员模板是函数模板。当在类模板外定义成员模板时,必须为类模板和函数模板提供模板形参列表。
template <typename T> // type parameter for the class
template <typename It> // type parameter for the constructor
Blob<T>::Blob(It b, It e): data(std::make_shared<std::vector<T>>(b, e)) { }
实例化与成员模板
int ia[] = {0,1,2,3,4,5,6,7,8,9};
vector<long> vi = {0,1,2,3,4,5,6,7,8,9};
list<const char*> w = {"now", "is", "the", "time"};
// instantiates the Blob<int> class
// and the Blob<int> constructor that has two int* parameters
Blob<int> a1(begin(ia), end(ia));
// instantiates the Blob<int> constructor that has
// two vector<long>::iterator parameters
Blob<int> a2(vi.begin(), vi.end());
// instantiates the Blob<string> class and the Blob<string>
// constructor that has two (list<const char*>::iterator parameters
Blob<string> a3(w.begin(), w.end());
控制实例化
当模板被使用时才生成实例化,这一事实意味着同样的实例可能会出现在多个对象文件中。当有多个独立编译的源文件使用相同的模板,并提供相同的实参时,每个文件中都会有该版本的一个实例。
在大型系统中,在多个文件中实例化相同模板的开销可能会变得非常大。在C++11标准下,可以通过显式实例化 (explicit instantiation) 来避免这种开销。显式实例化具有以下形式:
extern template declaration; // instantiation declaration
template declaration; // instantiation definition
declaration 是类或函数声明,其中所有的模板形参被替换成模板实参。例如,
// instantion declaration and definition
extern template class Blob<string>; // declaration
template int compare(const int&, const int&); // definition
当编译器遇到 extern
模板声明时,它不会在该文件中生成此实例化代码。将实例声明为 extern 承诺程序的其他地方有该实例化的非 extern 使用。对于给定的实例,可以有多个 extern 声明,但必须只有一个定义。
因为当使用模板时,编译器会自动对它实例化,所以 extern 声明必须出现在使用实例化之前。
// Application.cc
// these template types must be instantiated elsewhere in the program
extern template class Blob<string>;
extern template int compare(const int&, const int&);
Blob<string> sa1, sa2; // instantiation will appear elsewhere
// Blob<int> and its initializer_list constructor instantiated in this file
Blob<int> a1 = {0,1,2,3,4,5,6,7,8,9};
Blob<int> a2(a1); // copy constructor instantiated in this file
int i = compare(a1[0], a2[0]); // instantiation will appear elsewhere
// templateBuild.cc
// instantiation file must provide a (nonextern) definition for every
// type and function that other files declare as extern
template int compare(const int&, const int&);
template class Blob<string>; // instantiates all members of the class template
当编译器看到实例化定义(而不是声明)时,它将生成代码。因此,文件 templateBuild.o 将包含用 int 实例化的 compare 和 Blob<string> 类的定义。生成应用程序时,必须将 templateBuild.o 与 Application.o 文件链接一起。
警告:对于每个实例化声明,程序中必须有显式的实例化定义。
实例化定义实例化所有成员
类模板的一个实例化定义会实例化该模板的所有成员,包括内联成员函数。
注:只有类型可以用于模板的每个成员函数中,才可以使用实例化定义。
效率与灵活性
标准库智能指针类型为模板设计人员面临的设计选择提供了一个很好的示例。
shared_ptr 和 unique_ptr 明显的区别是,管理所存储指针的使用策略 —— shared_ptr 分享所有权,unique_ptr 独占所存储的指针。
这两个类另一个区别是,用户覆盖默认删除器的方式不同。当创建或 reset 指针时传递可调用对象可以很容易地覆盖 shared_ptr 的删除器。而删除器类型是 unique_ptr 对象类型的一部分。
删除器的处理方式不同之处在于这些类的功能。但是,实施策略的这种差异可能会对性能产生重要影响。
在运行时绑定删除器
shared_ptr 不是直接将删除器保存为成员,因为删除器的类型直到运行时才知道。
在编译时绑定删除器
unique_ptr 有两个模板形参,一个表示 unique_ptr 管理的指针,另一个表示删除器的类型。因为删除器的类型是 unique_ptr 类型的一部分,所以在编译时就知道删除器成员的类型。删除器可以直接存储在 unique_ptr 对象中。
通过在编译时绑定删除器,unique_ptr 避免了间接调用删除器的运行时开销。通过在运行时绑定删除器,shared_ptr 使用户能更容易覆盖删除器。