零、左值&右值
左值:能出现赋值号左边的值,能取地址,有名字的值就是左值;
右值:不是左值的值就是右值。右值又分为纯右值和将亡值。
一、移动语义
先看下面这段代码:有什么问题?
#include <iostream>
#include <string.h>
using namespace std;
class String
{
public:
String(const char* str = "")
{
if (str == nullptr) {
str = "";
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
String& operator=(const String& s)
{
if (this != &s) {
char *tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
}
return *this;
}
~String()
{
if (_str) {
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
String GetString(const char* str)
{
String tmp(str);
return tmp;
}
int main()
{
String s1("hiiiii");
String s2(GetString("hahhahahah"));
return 0;
}
这段代码其实是完全正确的可以执行的代码,但是程序猿写代码不是光没有bug就可以了,没有bug就要去提升性能了。
这段代码其实就是有性能缺失:看下图
这样就导致临时对象刚被销毁(空间被释放),s2在拷贝构造时又开辟空间。这样按常理好像多此一举,就好像别人给你递东西,本来直接递到你手上就行了,但是他却把东西先放在桌子上,然后你再从桌子上拿起来,多此一举。
所以在C++11引入了移动语义,就是避免这多一步的空间释放和开辟,将临时对象的资源转移到另一个对象中。要实现移动语义就必须要使用右值引用。
将拷贝构造函数改成如下形式:
String(String&& s)
:_str(s._str) //将临时对象的资源转交给新对象
{
s._str = nullptr; //让临时对象置空
}
二、C++11中的右值(纯右值&将亡值)
在C++11中,右值有两个概念:纯右值和将亡值;
- 纯右值
纯右值是C++98中右值的概念,用于识别临时变量和一些不跟对象关联的值。
比如:常量、一些运算表达式(1+2)等。 - 将亡值
生命周期将要结束的对象。比如值返回时的临时对象。
三、右值引用
右值引用的书写格式:类型&& 引用变量名称 = 实体
右值引用常用的几点:
- 与移动语义结合,减少没必要的资源开辟以提高代码的运行效率
比如上面的拷贝构造函数写成右值引用。 - 给一个匿名对象取别名,延长匿名对象的生命周期
例:
String GetString(char* str)
{
return String(str);
}
int main()
{
String&& s = GetString("hello"); //使用右值引用延长匿名对象的生命周期
return 0;
}
注意事项:
1、通常情况下,右值引用不能引用左值
2、右值引用和引用一样,定义时必须初始化。
四、std::move
std::move的功能就是将一个左值强制转换为右值,再通过右值引用引用该值,进而实现移动语义。
但是注意move的用处多用于生命周期即将结束的对象上。
std::move详解参考
五、完美转发
完美转发是指再函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另一个函数。
void Func(int t)
{
//...
}
template<typename T>
void Forward(T t)
{
Func(t);
}
再上述例子中,Forward函数是转发者,Func是实际目标函数。但是上述转发还不算太完美,因为完美转发是目标函数总希望将参数按照传递给转发函数的实际类型转给目标函数,而不产生额外的开销,就好像没有转发一样。
完美转发就是函数模板在向其他函数传递自身形参时,如果相应实参是左值,就应该转发为左值,如果实参是右值,就应该被转发为右值。然后根据转发来的参数的属性进行不同的处理(左值实现拷贝语义,右值实现移动语义)。
C++11通过forward实现完美转发,
使用方法:例:
void Fun(int &x)
{
cout << "左值引用" << endl;
}
void Fun(int &&x)
{
cout << "右值引用" << endl;
}
void Fun(const int &x)
{
cout << "const 左值引用" << endl;
}
void Fun(const int &&x)
{
cout << "const 右值引用" << endl;
}
template<typename T>
void PerfectForward(T &&t)
{
Fun(std::forward<T>(t));
}
int main()
{
PerfectForward(10); //右值引用
int a;
PerfectForward(a); //左值引用
PerfectForward(std::move(a)); //左值转化为右值引用
const int b = 7;
PerfectForward(b); //const左值引用
PerfectForward(std::move(b)); //const左值转化为右值引用
return 0;
}