首先声明,在C++代码中,应该为了使用方便而优化,千万不要变成--为了复杂化代码而优化的虚伪的 C++ 哲学。
C++ 中绝大部分直接的赋值操作都是传拷贝,因为传拷贝很安全。
其实在绝大多数情况下,传拷贝 没有一点问题。
对于C++代码而言,我始终认为 安全性 才是最重要的,比如const, constexpr关键字,这些东西必须要学,必不可少,这是为了代码规范。
但是对于右值引用,我不认为它是一个必须要学的东西,而且很多时候,我觉得右值引用会让人感到困惑,并且把代码复杂化了。
代码1:
const string lover{ "girls" };
const string& chaser{ lover };
对于一般的代码而言,这样使用string没有任何问题,这只是一个普通的左值引用。
接下来,我将化身刘谦,给诸位带来一场魔术表演,我这个表演打算让一个string变量--凭空消失。
我并不打算直接修改这个变量,而是我只需要通过std::move
别人问你,C++的艺术在哪里?C++的性能在哪里?你只需要向他展示这两行代码。
lover哪去了?凭空消失了?
不知道你懂了没有,C++在资源归属权的处理有着变态级别的控制。
拷贝就是拷贝,赋值也是拷贝,移动是移动,移动是资源易主。
再看一张图。
这串代码证明,在执行move操作后,string lover被清空了。
这个变量在内存中还存在,还可以使用,只是它的值为空。
By the way.
在使用std::move 之前 需要包含头文件 #include <utility>
代码3:
const string& hello(const string& str) {
return str;
}
int main() {
const string zyy = hello("great");
return 0;
}
但是对于这样的代码,仍然存在优化空间,你能发现吗?
形参拷贝到返回值,返回值再拷贝到zyy。
好家伙,两次拷贝
移动的优势在于,移动不开辟额外的内存。
如下。
string&& hello(string&& str) {
return move(str);
}
int main() {
const string zyy{ hello("great") };
return 0;
}
你也可以这样理解。
本来对于每个函数而言,执行函数时,一旦超过了它的作用域,局部变量就自动销毁了,但是我们通过一定的手段(右值引用)延长了“返回值的生命周期”,使得它没有被销毁,而是继续沿用下去。
const int& sum1(const int& a, const int& b) {
return a + b;
}
int&& sum2(int&& a, int&& b) {
return move(move(a) + move(b));
}
int main() {
constexpr int a{ 1 };
constexpr int b{ 2 };
const int jkj = sum1(a, b);
const int heh = sum2(3, 4);
cout << "jkj: " << jkj << endl;
cout << "heh: " << heh << endl;
return 0;
}
对于以上代码,sum2的效率更高,因为它做了3次move操作,取代了copy操作。
实际上,我们可以只写一个sum函数重载它们。
但这仍然太复杂,对于每个函数我们至少要两个模板。
一个是const T& ,还有一个是T&&
std::forward可以让这两者统一,本质上仍然是函数重载.
forward以后有机会再讲。