不同于函数模板,类模板在使用时候并不会有实参之类的数据供编译器推断模板参数,因此在使用时,必须显式提供<…>模板参数列表。
声明与定义
// 定义一个类模板
template<typename T>
class Fuck {
public:
void fuck();
// 类内定义成员
Fuck& fuck(T t){ // 类内,不需要写Fuck<T>,当然也可以写。
Fuck &ret = *this; // 类内不需要写Fuck<T>
return ret;
};
private:
std::shared_ptr<std::vector<T>> data; // 模板内再使用模板。
};
// 类外定义成员
template<typename T>
Fuck<T>& Fuck<T>::fuck(T t)
{
......
}
实例化
模板生成代码的过程就是实例化。
// 这里的<...>是显式模板实参列表(explicit template argument list)
Blob<int> ia; // 在使用时,实例化模板,int版本的代码(代码1),本质就是定义了一个类。
Blob<string> ia; // 在使用时,实例化模板,string版本的代码(代码2),定义了第二个类。
Blob<double> ia; // 在使用时,实例化模板,double版本的代码(代码3),定义了第三个类。
// 下面是Blob<int>实例化后的类模板。
template <>
class Blob<int> {
......
}
显式实例化
模板在被使用到时才实例化,这是被动的。我们也可以主动控制模板的实例化。这样可以避免在多个独立编译的文件中有相同的模板实例,造成严重额外开销。
就好像每个文件都独自定义变量,可以借鉴变量的extern跨文件访问特性。
extern template declaration; // 实例化的声明,表示我用到了外部的一个模板实例。
template declaration; // 定义一个模板实例,
// declaration是一个实例化的模板,不是模板。
// **********************************************************************************
// ******** 例 子
// **********************************************************************************
// 声明Blob<string>模板实例
// 该实例在其他文件被定义了。编译器会链接定义该实例的文件。
extern template class Blob<string>;
// 定义函数模板实例
// 这里是已经产生代码了,相当与使用了一次compare(1, 2)
template int compare(const int&, const int&);
// 定义类模板实例,这里会实例化类内部的所有成员(包括成员模板函数)
template class Blob<string>;
成员函数
类模板的成员函数既可以定义在类模板内部,也可以在外部。在内部时,和普通类的成员函数定义一样的代码。当在类外定义成员函数时,则每一个定义前面都需要添加template声明。
类模板的成员函数不能是虚函数。
template<typename T>
class Blob {
public:
Blob();
Blob& fuck();
// 类内定义成员函数
void shit(){
Blob *tmp = this;
Blob<T> *tmp1 = this; // 同上等价。在类内可显式指定实参,也可不用。
}
}
// 类外定义成员函数
template<typename T> // 必须有template<...>起始声明
Blob<T>& Blob<T>::fuck(){ // 在类外部使用模板名必须指定实参,即Blob<T>
Blob p = *this; // 函数体相当于在类内部,无需指定实
// 只要能直接访问this,就是在类内部。
Blob<T> p = *this ; // 与上等价。
return *this;
}
// 错误,没有template<...>模板起始声明
void Blob::fuck(){ // 错误,Blob必须是写Blob<int>
}
template<typename T>
void Blob<T>::Blob(){ // 构造函数也一样声明。
}
实例化
默认情况下, 一个类模板的成员函数只有当程序用到它时才进行实例化。如果一个成员函数没有被使用,则它不会被实例化。成员函数只有在被用到时才进行实例化,这—特性使得即使某种类型不能完全符合模板操作的要求,我们仍然能用该类型实例化类。
友元
类模板的友元可以是模板或者非模板。
非模板类的友元可以是模板类或者非模板。
类模板的友元是模板
如果两者之间的模板参数列表相同时,则友元的实例是对应相同实例化的类模板实例的友元,一一对应关系。
// 模板的前置声明,使用之前先声明,无需写出模板参数名字。
template<typename> class BlobPtr;
template<typename> class Blob;
template<typename> bool operator==(const Blob<T>&, const Blob<T>&);
template<typename T>
class Blob {
// T实例化的Blob和T实例化的BlobPtr是友元(一一对应)
friend class BlobPtr<T>;
// T实例化的Blob和T实例化的BlobPtr是友元(一一对应)
friend bool operator==<T>(const Blob<T>&, const Blob<T>&);
};
int main(){
Blob<char> bpc;
// BlobPtr<char>是Blob<char>的友元。
// operator==<char>是Blob<char>的友元。
Blob<int> bpc;
// BlobPtr<int>是Blob<int>的友元。
// operator==<int>是Blob<int>的友元。
// BlobPtr<char>不是Blob<int>的友元。
// operator==<char>不是Blob<int>的友元。
return 0;
}
如果两者之间的模板参数列表不相同时,则友元的所有实例是类模板每个实例的友元,多对多的友元关系。
类模板的友元是非模板
该非模板友元是类模板所有实例的友元。
template<typename T>
class Fuck {
friend class Shit; // Shit类是Fuck所有实例的友元,不需要前置类声明。
}
普通类的友元是模板
- 情形一,当模板是实例化的模板时,是一对一的友元关系。
- 情形二,当模板是非实例化模板时,是多对一的关系,即模板的所有实例都是该类的友元。 ```cpp
template
class C {
// 情形一
friend class Fuck<C>; // 用类C实例化的Fuck是C的友元。
// 情形二
template<typename T>
friend class Shit; // Shit的所有实例是类C的友元,无需前置声明。
}
<a name="jJMvY"></a>
## 模板参数为友元
在新标准中,可以将模板类型参数声明为友元。
```cpp
template <typename Type>
class Bar {
friend Type; // 将访问权限授予用来实例化Bar的类型Type
};
Bar<Foo> fuck; // Foo是Bar<Foo>的友元。
类型别名模板
类模板的一个实例是一种类型,因此类模板的实例可以有类型别名,类模板不能有类型别名,因为类模板不是类型,但是可以有类型别名模板!
// 类模板的实例可以有类型别名
typedef Blob<string> StrBlob;
// 类模板没有类型别名
typedef Blob<T> BlobT; // 错误。
// 可以有类型别名模板
template<typename T>
using twin = pair<T, T>
template<typename T>
using twin1 = pair<T, unsigned>
twin<string> authors; // authors是一个pair<string, string>
twin1<string> books; // books是一个pair<string, unsigned>
static成员
类模板的一个实例对应一个类类型,类与static成员是一一对应,即一个类的一个static成员有且只有一个定义。
类似任何其他成员函数,一个static成员函数只有在使用时才会实例化。
template <typename T>
class Foo {
public:
static std::size_t count() { return ctr; }
private:
static std::size_t ctr;
};
// 类外定义并初始化静态成员
template< typename T>
size_t Foo<T>::ctr = 0;
// 实例化Foo<string>
// 实例化static成员Foo<string>::ctr
// Foo<string>::count需要在使用到的时候实例化。
Foo<string> fs;
// 所有三个对象共享相同的 Foo<int>::ctr、Foo<int>::count成员
Foo<int> f1, f2, f3;
Foo<int> fi; // 实例化 Foo<int> 类和 static 数据成员 ctr
auto ct = Foo<int>::count(); // 实例化 Foo<int>::count
ct = fi.count(); // 使用 Foo<int>::count
ct = Foo::count(); // 错误:使用哪个模板实例的count?
特例化
注意,特例化与实例化的区别:
实例化:生成模板代码的过程。
特例化:为模板的特定类型,重定义模板内部逻辑。
一个是生成代码,一个是重定义逻辑,作特殊处理或优化。
以std::hash为例,std::hash的特例化必须完成以下定义:
- 重载调用运算符
- 返回值类型size_t,参数类型容器关键字类型。
- 拷贝控制成员和构造函数
- 可采用默认合成的。
两个类型成员
- result_type:调用运算符的返回类型
- argument_type:调用运算符参数类型 ```cpp // 打开std 命名空间,特例化std::hash namespace std {
// 定义std::hash的一个特例化版本,模板参数为Sales_data template<>
struct hash{ // 必须定义以下类型 typedef size_t result_type; typedef Sales_data argument_type; // 默认情况下,此类型需要支持== size_t operator()(const Sales_data& s) const; // 使用合成的拷贝控制和构造函数 };
size_t hash
::operator()(const Sales_data& s) const{ return // 内置类型的特例化版本已经在std标准库内定义
hash<string>()(s.bookNo) ^
hash<unsigned>()(s.units_sold) ^
hash<double>()(s.revenue);
} } // 关闭std命名空间;注意,右花括号之后没有分号
// 别忘了把该特例化声明为Sales_data的友元。
class Sales_data{
friend class std::hash
int main()
{
// 内部使用特例化的std::hash
}
<a name="KHCRO"></a>
## 部分特例化
与函数模板不同,类模板的特例化不必为所有模板参数提供实参。我们可以只指定一部分而非所有模板参数,或是参数的一部分而非全部特性。一个类模板的部分特例化(partial specialization) 本身是一个模板,使用它时用户还必须为那些在特例化版本中未指定的模板参数提供实参。
```cpp
// 原始的、最通用的版本
template <typename T>
struct remove_reference {
typedef T type;
}
// 部分特例化版本,用于左值引用。
template <typename T>
struct remove_reference<T&>{ // 左值引用
typedef T type;
};
// 部分特例化版本,用于右值引用。
template <typename T>
struct remove_reference<T&&>{ // 右值引用
typedef T type;
};
int i;
// decltype(42)为int, 使用原始模板
remove_reference<decltype(42)>::type a;
// decltype(i)为int&,使用第一个(T&)部分特例化版本
remove_reference<decltype(i)>::type b;
// decltype(std::move(i))为int&&,使用第二个(即T&&)部分特例化版本
remove_reference<decltype(std::move(i))>::type c;
特例化成员
特例化部分成员函数而不是特例化整个模板。
template <typename T>
struct Foo {
Foo(const T &t = T()) :mem(t){}
void Bar () {}
T mem;
};
// 特例化成员
template<>
void Foo<int>::Bar() { // 特例化Foo<int>的成员Bar
}
Foo<string> fs; // 实例化Foo<string>::Foo()
fs.Bar(); // 调用Foo<string>::Bar()方法
Foo<int> fi; // 实例化Foo<int>::Foo()
fi.Bar(); // 调用Foo<int>::Bar(),上面的特例成员。
成员模板
类(普通类、类模板)的成员可以是模板,这种成员叫模板成员(member template),不能是虚函数。
普通类的成员模板
struct A{
template<typename T>
void shit(T t); // shit是A的模板成员。
}
A a;
a.shit(1.0);
类模板的成员模板
两个模板之间可以有独立的模板参数。
template <typename T>
class Blob {
// 构造函数是成员模板。It迭代器类型
template <typename It>
Blob(It b, It e};
};
// 在类外部定义成员模板,必须带上两个模板参数列表,先类的,再成员自己的。
template <typename T> // 先模板类的模板参数
template <typename It > // 在成员模板构造函数的模板参数
Blob<T>::Blob(It b, It e) {
}
int ia[];
vector<long> vi;
// 成员模板(函数)可以根据实参自动推断类型
// 类类型需要显式指定模板实参。
Blob<int> f(begin(ia), end(ia));