比如一个func函数,需要传入一个T类型的很大的x参数用于func内部读取(不修改)。有几种方法:
方法一:值传递
这也是最简单的方法:
void func(T x) { T y = x; // x到y有拷贝 T y = std::move(x); // x所有权交给y,没有拷贝,但后文不能再用x了 }调用:
T a; func(a); // a到x有1次拷贝由于是值传递,这里a到x需要一次拷贝,x到y如果不用std::move的话又有一次拷贝。
如果不想要a到x的值传递时的拷贝,需要在调用时move:
T a; func(std::move(a)); // a到x有0次拷贝,1次move构造(move比较轻量) //这里把a完全交给了func内部,没有拷贝,但后文不能再用a了结论:
接口最灵活
调用方决定是否拷贝(选择是否std::move(a)),这是 STL 和 Effective Modern C++ 强烈推荐的模式
现代 C++ 非常推荐(“pass-by-value + move”惯用法)
方法二:引用传递(只读参数最佳)
类似于c语言的传递指针(不利于对象管理,此处不讨论),但是c++中有一个引用传递的新概念:
void func(const T& x) { T y = x; // x到y有拷贝 }调用:
T a; func(a); // x就是a的引用,0次拷贝,0次move构造这里T&表示传入的就是a的引用,不存在a到x的拷贝过程。const表示传入的x在函数内部不准修改,保证了a不会被func修改掉(func只读a)。
但是const T&不能实现a到y的完全无拷贝传递,一个经典的错误:
void func(const T& x) { // 假的move,实际还是发生了拷贝,因为move的是const T&类型,而不是x的所有权 T y = std::move(x); }结论:
最佳适用场景
只读
不接管所有权
不需要在函数内部生成新对象
不要指望它“零拷贝生成 y”
方法三:传入右值(所有权传递)
针对const T&不能传入a的所有权从而无拷贝把a给到y的问题,实际应当传入a的右值。
有两种方法:
1. 方法一种提到的值传递,函数调用一次move把a给到x,函数内部用move再把x给到y,全程0拷贝,2次move(推荐)
void func(T x) { y = std::move(x); //x到y 0次拷贝,1次move(轻量),后文不能再用x了 } //调用 T a; func(std::move(a)); // a到x 0次拷贝,1次move(轻量),后文不能再用a了这相当于把是否拷贝的选择交给了调用方,调用方可以用func(a)也可以用func(std::move(a)),前者a到x有一次拷贝,后者没有但后文不能再用a了。
2. 强制只能传入右值,明确接管
void func(T&& x) { T y = std::move(x); // x到y有0次拷贝,1次move(轻量) } //调用 T a; func(std::move(a)); // a到x 0次拷贝,0次move,因为x不是新对象,已经绑定到a // func(a); //不允许这里用T&&表示传入的只能是右值,减少了值传递a到x的move,但也限定了a后文不能再用。
结论:
适合:
明确“我就是要接管你的对象”,相比值传递写法可以少1次move。
函数语义就是“消费型接口”
不适合:
公共 API
希望调用方既能传左值又能传右值
所以:T&&是“语义最强,但最不灵活”的接口。
总结:
只读 →
const T&可能接管 →
T(值传递或者std::move())强制接管 →
T&&别对
const T&用std::move