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类型。
参考资料
- 飘零的落花 - 现代C++详解
- Modern-Cpp-templates-tutorial
- C++20高级编程