3 - 函数模板,成员函数模板

本文主要介绍了独立函数的模板和类中成员函数的模板该如何编写。

函数模板

可以为独立函数编写模板,在现代 C++ 中,这是一个 一直普遍使用 的技术。

定义

和类模板类似:

   
namespace mystd { // 模拟for-each算法 template<typename iter_type, typename func_type> void for_each(iter_type first, iter_type last, func_type func) { for (auto iter = first; iter != last; ++iter) { func(*iter); } } }

在名称空间 mystd 中,编写了一个类似于 for-each 语法的函数模板。该函数模板可以对元素从头到尾遍历,并完成自定义函数 func() 的工作。

需要注意的是:函数模板不是函数,它必须经过实例化才能生成实际的函数定义

实例化

可通过两种方式实例化这个函数:一种是通过尖括号显式地指定类型;另一种是忽略类型,让编译器根据里边的参数自行推导(编译期完成):

   
std::vector<int> iVec{ 1, 2, 3, 4, 5 }; // 显式指定类型 mystd::for_each<std::vector<int>::iterator, void(*)(int&)>(iVec.begin(), iVec.end(), [](int& elem) { ++elem; }); // 忽略类型,让编译器自行推导 mystd::for_each(iVec.begin(), iVec.end(), [](int& elem) { ++elem; });

我们定义了一个 vector<int>,使用 for_each 函数模板,前两个参数是迭代器的开始和末尾,最后一个参数则是自定义的 lambda 表达式,可以让容器里的元素自增 1。

重载

   
template<typename T> void test(T) { std::puts("template"); } void test(int) { std::puts("int"); } test(1); // 匹配到test(int) test(1.2); // 匹配到模板 test("1"); // 匹配到模板

如上述代码所示,函数模板和非模板函数间可以重载。通常优先选择非模板的函数,具体规则详见重载决议

成员函数模板

C++ 允许模板化类中的单个方法,可以在类模板中进行,也能在非模板化的类中进行。成员函数模板对赋值运算符和拷贝构造函数非常有用。

需要注意的是:不能用方法模板编写虚方法和析构函数

定义

   
template<typename T> class MyVector { public: template<typename T2> void outPut(const T2& elem); }; // 类外定义 template<typename T> template<typename T2> void MyVector<T>::outPut(const T2& elem) { std::cout << elem << std::endl; }

首先定义了类模板 MyVector,里边有个成员函数模板 outPut()

使用

mystd::MyVector<std::string> myVec;
myVec.outPut(233);

可以正确输出 233,因为该成员函数主要看的是 T2 类型,而不是 T 类型。

用法

赋值运算符和拷贝构造函数

假设有一类模板 Test,它的拷贝构造函数和赋值运算符如下:

Test (const Test& src);
Test& operator=(const Test& rhs);

假设 T 为 double,那么上边的代码就会生成 Test<double> 专属的,此时参数不能为 Test<int>,即使 int 可以强转为 double

现在成员函数模板便派上用场,只需 再增加模板化的赋值运算符和拷贝构造函数即可

   
template<typename E> Test (const Test<E>& src); template<typename E> Test<T>& operator=(const Test<E>& rhs);

现在就能接收 Test<int> 类型的参数了。注意不要删除原来的拷贝构造函数和赋值运算符,因为如果 E 和 T 类型相同,编译器还是会调用原来的。并且,赋值运算符接收 E 类型,但返回 T 类型

参考资料