7.4 成员函数模板
简介
Tips:成员模板不能是虚函数。
一个类(无论是普通类还是类模板)可以包含本身是模板的成员函数,这种成员被称为成员模板。
普通类的成员模板
我们定义一个类,类似unique_ptr
使用的默认删除器类型,我们的类将包含一个重载的函数调用运算符,它接受一个指针并对此指针执行delete
。与默认删除器不同,我们的类还将在删除器被执行时打印一条信息。由于希望删除器适用于任何类型,所以我们将调用运算符定义为一个模板:
#include <iostream>
#include <memory>
// 函数对象类, 对给定指针执行delete
class DebugDelete {
public:
explicit DebugDelete(std::ostream &s = std::cerr) : os_(s) { }
template <typename T> void operator()(T *p) const {
os_ << "deleting unique_ptr" << std::endl;
delete p;
}
private:
std::ostream &os_;
};
int main() {
// 在一个临时对象上调用operator()(int*)
double *pd = new double;
DebugDelete()(pd);
// 将DebugDelete用作unique_ptr的删除器
// 实例化DebugDelete::operator()<int>(int*)
std::unique_ptr<int, DebugDelete>p(new int, DebugDelete());
return 0;
}
类模板的成员模板
对于类模板,我们也可以为其定义成员模板。在此情况下,类和成员各自有各自的、独立的模板参数。
#include <iostream>
#include <memory>
#include <vector>
#include <list>
#include <string>
// 定义Foo的构造函数, 接受两个迭代器表示要拷贝的元素范围
template <typename T> class Foo {
public:
template <typename It> Foo(It b, It e);
private:
std::shared_ptr<std::vector<T>> data_;
};
template <typename T>
template <typename It>
Foo<T>::Foo(It b, It e) : data_(std::make_shared<std::vector<T>>(b, e)) { }
int main() {
// 实例化Foo<int>类的接受两个int*参数的构造函数
int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
Foo<int> f1(std::begin(ia), std::end(ia));
// 实例化Foo<int>类的接受两个vector<long>::iterator参数的构造函数
std::vector<int64_t> vi = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
Foo<int> f2(vi.begin(), vi.end());
// 实例化Foo<string>类的接受两个list<const char*>::iterator参数的构造函数
std::list<const char*> w = {"tomo", "cat", "tomocat"};
Foo<std::string> f3(w.begin(), w.end());
}
7.5 可变参数模板
简介
一个可变参数模板指的是一个接受可变数目参数的函数模板或类模板,其中可变数目的参数被称为参数包。存在两种参数包:
- 模板参数包:零个或多个模板参数
- 函数参数包:零个或多个函数参数
实例
// Args是一个模板参数包; rest是一个函数参数包
// Args表示零个或多个模板参数类型
// rest表示零个或多个函数参数
template <typename T, typename... Args>
void foo(const T &t, const Args&... rest);
给定下面的调用:
int i =0; double d = 3.14; string s = "tomocat";
foo(i, s, 42, d); // 包中有三个参数
foo(s, 42, "cat"); // 包中有两个参数
foo(d, s); // 包中有一个参数
foo("cat"); // 空包
编译器会为foo
依次实例化出四个不同的版本:
void foo(const int&, const string&, const int&, const double&);
void foo(const string&, const int&, const char[4]]&);
void foo(const double&, const string&);
void foo(const char[4]);
sizeof…运算符
当我们需要知道包中有多少元素时,可以使用sizeof...
运算符,它返回一个常量表达式,而且不会对齐实参求值:
template <typename... Args> void f(Args... args) {
cout << sizeof...(Args) << endl; // 模板参数的数目
cout << sizeof...(args) << endl; // 函数参数的数目
}
实战: print函数
我们定义一个print
函数,用于在给定流上打印给定实参列表的内容。可变参数函数通常是递归的,第一步调用处理包中的第一个实参,然后用剩余实参调用自身。我们的print
函数也是这样的形式,每次递归调用将第二个实参打印到第一个实参表示的流中。为了终止递归,我们还需要定义一个非可变参数的print
函数,它接受一个流和一个对象:
#include <iostream>
#include <string>
// 用来终止递归并打印最后一个元素的函数
template <typename T>
std::ostream &print(std::ostream &os, const T &t) {
return os << t << std::endl; // 包中最后一个元素打印换行符
}
// 包中除最后一个元素之外的其他元素都会调用这个版本的print
template <typename T, typename... Args>
std::ostream &print(std::ostream &os, const T &t, const Args&... rest) {
os << t << ", ";
return print(os, rest...);
}
int main(void) {
int i =0; double d = 3.14; std::string s = "tomocat";
print(std::cout, i, s, 42); // 输出0, tomocat, 42
print(std::cout, s, 42); // 输出tomocat, 42
print(std::cout, 42); // 输出42
}
对于print(std::cout, 42)
这一次调用而言,两个模板函数都提供同样好的匹配。但是非可变参数模板比可变参数模板更加特例化,因此编译器选择非可变参数版本。
Tips:当定义可变参数版本的
参数包扩展
对于一个参数包,除了获取其大小外,我们能对它做的唯一的事情是扩展它,我们通过在模式右边放一个省略号...
来触发扩展操作。例如我们的print
函数包含两个扩展:
template <typename T, typename... Args>
// 扩展Args
std::ostream &print(std::ostream &os, const T &t, const Args&... rest) {
os << t << ", ";
// 扩展rest
return print(os, rest...);
}
除了上面这种简单的扩展外,C++语言还允许更复杂的扩展模式。例如我们可以编写第二个可变参数函数,对其每个实参调用debug_rep
,然后再调用print
打印结果:
// 在print调用中对每个实参调用debug_rep
template <typename... Args>
std::ostream &errorMsg(std::ostream &os, const Args&... rest) {
// print(os, debug(a1), debug(a2), ..., debug_rep(an))
return print(os, debug_rep(rest)...);
}
调用方式如下:
errorMsg(std::cerr, 10, "cat", 3.14);
// 等价于
print(std::cerr, debug_rep(10), debug_rep("cat"), debug_rep(3.14));
注意下面这种模式会编译失败:
// 错误的写法: 此调用无匹配函数
// 相当于print(os, debug_rep(a1, a2, ..., an))
// 但是并不存在接受五个参数的debug_rep函数
print(os, debug_rep(rest...));
转发参数包
在C++11新标准下,我们可以组合使用可变参数模板与forward
机制来编写函数,实现将其实参不变地传递给其他函数。
// fun有零个或多个参数, 每个参数都是一个模板参数类型的右值引用
// fun的参数是右值引用, 因此我们可以传递给它任意类型的实参
template <typename... Args>
void func(Args&&... args) {
work(std::forward<Args>(args)...);
}