值类型
每一个C++表达式都有一个 “值类型”——它是编译器在表达式计算过程中创建、复制和移动临时对象时必须遵循的规则的基础。例如:
- “=”左边的值就是 “左值(lvalue)”,计算时其地址发挥了作用。
- “=”右边的值就是 “右值(rvalue)”,计算时其数据发挥了作用。
除此之外还有其他的 “值类型”,他们之间的关系如下:
详见Value Categories: Lvalues and Rvalues (C++) | Microsoft Docs
左值引用
在 “右值引用” 出现之前,只有一种“引用”,和“指针”的性质类似,实际掌握了所引用变量的地址,例如:
#include<iostream>
using namespace std;
int main()
{
int x = 3;
int& r = x;
r = 7;
cout << x << endl;
}
这里r
是x
的引用,因此让它的值改变等价于让x
的值改变。
在 “右值引用” 出现之后,上面这种引用被称为 “左值引用”,因为它所指代的是一个可被用户改变的“左值”。
Lvalue 引用声明符: & | Microsoft Docs
右值引用想要解决的问题
假设有一个类class TestObject
。
情况(1)
int main()
{
TestObject a;
TestObject b = a;
}
情况(2)
TestObject GetObject()
{
TestObject t = TestObject();
return t;
}
int main()
{
TestObject b = GetObject();
return 0;
}
假如不存在 “右值引用”,那么考虑b
在构造时,情况(1)和(2)是对其是等价的,都会调用“拷贝构造函数”。然而实际上,情况(1)和(2)是不同的:
- 在情况(1)中,是用
a
来对b
构造,而a
是另一个用户可访问的对象。 - 在情况(2)中,是用
GetObject()
返回的对象来对b
构造,而GetObject()
返回的对象实际是一个临时对象,是用户不可访问的。
可见假如不存在 “右值引用”,则上述两者情况是无法区分的。而如果能区分,则会让一些性能优化成为可能。
右值引用
“右值引用” 使用&&
来表示。
有了它,便可以定义移动构造函数和移动赋值运算符。例如TestObject
定义:
class TestObject
{
public:
TestObject()
{
cout << "构造函数" << endl;
}
TestObject(TestObject& Ohter)
{
cout << "拷贝构造函数" << endl;
}
TestObject(TestObject&& Ohter)
{
cout << "移动构造函数" << endl;
}
};
这样再回头看刚才的那两种情况:
情况(1)输出:
构造函数
拷贝构造函数
情况(2)输出:
构造函数
移动构造函数
可以看到二者针对不同的情况可以得到区别。
关于右值引用的官方说明详见:
Rvalue Reference Declarator: && | Microsoft Docs
使用移动构造函数优化
正如官方文档所说:利用移动语义,你可以编写将资源(如动态分配的内存)从一个对象转移到另一个对象的代码。
例如TestObject
有一个数据data
,那在拷贝构造函数中和移动构造函数中对其的处理应该是不同的:
class TestObject
{
int* data;
public:
TestObject()
{
cout << "构造函数" << endl;
data = new int[100];
}
TestObject(TestObject& Other)
{
cout << "拷贝构造函数" << endl;
memcpy(Other.data, data, 100 * sizeof(int));
}
TestObject(TestObject&& Other)
{
cout << "移动构造函数" << endl;
delete[] data;
data = Other.data;
Other.data = nullptr;
}
};
- 拷贝构造函数中应该是真的将
data
复制出一份新的 - 而移动构造函数只是将另一个对象的指针拿来。这不会造成问题,因为另一个对象本身就是临时对象。
std::move
无条件将其自变量强制转换为右值引用。
template <class Type>
constexpr typename remove_reference<Type>::type&& move(Type&& Arg) noexcept;
例如:
int main()
{
TestObject a;
TestObject b = std::move(a);
}
将会输出:
构造函数
移动构造函数
当然,使用了std::move
也就意味着变量被转移,那么它就不应该在接下来继续使用了。
参考资料
有些的理解可能不准确。下面是一些参考资料:
权威的官方文档:
右值引用 | Microsoft Docs
看起来比较权威的资料:
A Brief Introduction to Rvalue References