C++右值引用
什么是右值
判断其为左值还是右值并不能单纯通过其在等号的左边还是右边。
通俗的定义为左值是非临时对象,在表达式结束后依然存在,而右值为临时对象,仅对当前表达式有效。
定义int a=1; 判断1,a,a++,++a是左值还是右值?
1是立即数,是临时对象,为右值。
a在表达式结束后依然存在,不是临时对象,为左值。
a++是取出a的拷贝,然后对a加一,返回拷贝,是临时对象,为右值。
++a是对a加一,然后返回a,不是临时对象,为左值。
左值的声明符号为”&”,右值的声明符号为”&&”。
可以通过下面的代码,来对于上述结果进行验证。
template <class T> void print(T & t)
{
cout << "LValue: " << t << endl;
}
template <class T> void print(T && t)
{
cout << "RValue: " << t << endl;
}
int main()
{
print(1);
int a = 1;
print(a);
print(a++);
print(++a);
return 0;
}
输出结果为
RValue: 1
LValue: 1
RValue: 1
LValue: 3
右值引用的意义
右值引用的意义是为了解决对于不必要临时对象的生成与拷贝而造成的效率降低的问题。
下面给出一个例子
class MyStr
{
private:
char* str;
public:
MyStr()
{
cout << "MyStr()" << endl;
str = NULL;
}
MyStr(const char* s)
{
cout << "MyStr(const char*)" << endl;
str = new char[strlen(s) + 1];
strcpy(str, s);
}
MyStr(const MyStr &s)
{
cout << "MyStr(const MyStr&)" << endl;
str = new char[strlen(s.str) + 1];
strcpy(str, s.str);
}
MyStr& operator =(const MyStr &s)
{
cout << "MyStr& operator=(const MyStr&)" << endl;
str = new char[strlen(s.str) + 1];
strcpy(str, s.str);
return *this;
}
~MyStr()
{
cout << "~MyStr()" << endl;
if (str)
{
delete[] str;
str = NULL;
}
}
};
int main()
{
MyStr a;
a = MyStr("2");
}
输出结果为
MyStr()
MyStr(const char*)
MyStr& operator=(const MyStr&)
~MyStr()
~MyStr()
可以看出,采用传统方法在执行a = MyStr(“2”);步骤时,先申请存储空间,写入“2”,再在a中申请存储空间,调用strcpy,将“2”拷贝到a中申请的存储空间,最后将临时存储空间释放。
这样对于临时存储空间的申请,再释放的过程,可以说是对于效率的一种损耗。试想,能否直接将写入“2”以后的临时存储空间交给a,而不再做申请存储空间,赋值再删除的工作。
由此引入了右值引用。
加入右值引用的代码
MyStr(MyStr &&s)
{
cout << "MyStr(const MyStr&&)" << endl;
str = s.str;
s.str = NULL;
}
MyStr& operator =(MyStr &&s)
{
cout << "MyStr& operator=(const MyStr&&)" << endl;
delete[] str;
str = s.str;
s.str = NULL;
return *this;
}
可以看出,其中的赋值操作由str = new char[strlen(s.str) + 1];strcpy(str, s.str);变为了str = s.str;使得拷贝操作由深拷贝变为浅拷贝,从而提高效率。
采用了右值引用后,输出结果为:
MyStr()
MyStr(const char*)
MyStr& operator=(const MyStr&&)
~MyStr()
~MyStr()
可以看出a = MyStr(“2”);步骤中,采用了右值引用的特性。
在使用右值引用时,还需要注意两点:
1. 右值不可以是常量,因为我们需要修改右值。
2. 右值的资源链接和标记必须修改。否则,右值的析构函数就会释放资源。转移到新对象的资源也就无效了。
move语义
std:move用于将左值引用转化为右值引用。可以利用右值引用的特性,优化效率。
如swap函数,交换两个参数的值。通常采用的方式是备份,再交叉赋值,是一种深拷贝的方式。
template <class T> swap(T& a, T& b)
{
T t(a);
a = b;
b = t;
}
采用move语义,将左值引用转化为右值引用。
template <class T> swap(T& a, T& b)
{
T t(std::move(a));
a = std::move(b);
b = std::move(t);
}
通过这种方式就避免了3次不必要的拷贝操作。
所有代码均通过Visual Studio 2015调试