现代C++模板教程
看了这个教程,自己记录一些内容,不一定靠谱。
为什么需要模板?简单来说就是为了少写点重复的代码,让编译器自动帮我们生成。为了少写点函数,就有了函数模板
,为了少写点类,就有了类模板
。有时模板可能不适用于一些特殊的情况,就需要对模板进行 特化
。有时我们还想限制模板参数的范围,就需要对模板参数进行约束
。
函数模板
初始函数模板
首先我们思考一个问题,函数模板是函数吗?
⭐️函数模板其实不是函数,只有对函数模板进行实例化
,它才是一个函数
这是一个简单的函数模板,返回两个对象中较大的那一个
#include <iostream>
template<typename T> // 模板形参列表
T max(T a, T b) {
return a > b ? a : b;
}
struct Test {
int v_{};
Test() = default;
Test(int v) : v_(v) {}
bool operator>(const Test& t) const {
return this->v_ > t.v_;
}
};
int main() {
int a = 1, b = 1;
std::cout << "max(a, b): " << ::max(a, b) << std::endl;
Test ta(10), tb(20);
std::cout << "max(ta, tb): " << ::max(ta, tb).v_ << std::endl;
}
用 compile explorer
看一下,可以看到编译器在编译期
对函数模板进行了实例化,生成的函数为 int max<int>(int, int)
和 Test max<Test>(Test, Test)
,
<模板参数>推导
使用函数模板时,编译器可以由传入的参数自动推导模板参数
。例如max(a, b)
,传递了两个int
类型参数a
和 b
,我们没有显式指定模板参数,编译器自动推导得到模板参数 T 为 int
但也不是什么情况下都可以进行自动类型推导的,例如 a
和 b
两个类型不同,这时需要我们显式指定模板参数
,例如 max<int>(a, b)
int a = 1;
double b = 2.3;
// std::cout << "max(a, b): " << ::max(a, b) << std::endl; // 无法进行自动类型推导
std::cout << "max(a, b): " << ::max<int>(a, b) << std::endl; // 2
std::cout << "max(a, b): " << ::max<double>(a, b) << std::endl; // 2.3
引用折叠/万能引用/完美转发
所谓模板参数推导,这里推导的模板参数
是一个类型
,例如 int
或者自定义的类型,在C++中谈到类型那就无法避开左值引用
与右值引用
对于右值引用
&&
以及std::move()
我的理解:右值引用int&&
最常用于需要处理临时对象或大对象的场景,例如转移一块动态内存的所有权,std::move
是将类型转换为右值引用,但实际上这个所有权的转移
不是std::move
来做的,而是由移动构造函数
和移动赋值函数
来做的,std::move
只是将变量转换成右值引用,而这个所谓的&&
符号也只是为了能匹配到移动构造/赋值函数
,一个简单的移动构造函数如下,它转移了动态内存块的所有权:
Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) { // 转移资源
other.data = nullptr;
other.size = 0;
}
这里提一下
int&&
,这是一个右值引用
,字面值常量是右值,那将int&&
绑定到一个字面值常量的本质是啥?我们直接看编译器生成的汇编代码,可以发现以下这两段代码完全等价
分析:b 是一个右值引用,引用的是字面量 10,编译器会接把 10 这个常量值放到栈空间中,并将 b 绑定到这个地址int && b = 10;
int a = 10; int &b = a;