- 1. 基本概念
- 2. 移动构造和移动赋值
- 3. 完美转发
- 参考资料
| 类型 | 释义 | 区别 | 例子 |
|---|---|---|---|
| 左值 | 表示数据的表达式 | 可以取地址、可以赋值 | 变量、解引用的指针(特殊:const修饰的变量不可被赋值) |
| 右值 | 表示数据的表达式 | 不能取地址 | 字面常量、表达式返回值、传值返回的返回值 |
| 左值引用 | 给左值取别名 | 左值引用不可直接引用右值 (const除外) | int a; int &b = a; |
| 右值引用 | 给右值取别名 | 右值引用不可直接引用左值 (move除外) | int a = 1; int b = 2; int&& c = a + b; |
#includeint add(int a, int b) { return a + b; } int main() { //左值 a、b、c、p、*p int a = 1, b = 2; const int c = 3; int* p = &a; //右值 1、a+b、add(a, b) int d = a + b; int e = 1; int f = add(a, b); // 左值引用+const 可以引用右值 const int& a1 = 1; const int& a2 = a + b; const int& a3 = add(a, b); //右值引用可以引用move后的左值 int&& b1 = std::move(a); const int&& b2 = std::move(c); int*&& b3 = std::move(p); int&& b4 = std::move(*p); return 0; }
补充:
- 右值中自定义类型的临时对象、匿名对象称为将亡值;将亡值在调用结束后就会被销毁,但若用一个const变量指向这个将亡值,则将亡值的生命周期将延长到与const变量相同的生命周期。
- 右值不可取地址,但是右值的别名可以取地址(右值引用后,会将右值存到新特定的空间,这个空间地址是可以被获知的);右值引用是一个左值,可以被赋值。
int&& a = 1; std::cout << &a << std::endl; a = 2; std::cout << a << std::endl;2. 移动构造和移动赋值
-
出了作用域对象还在,则可以使用左值引用返回;若出作用域后对象不在了,则不能用左值引用返回(对临时对象{一般在上层调用函数的栈或寄存器中}的引用会造成非法空间的访问),只能传值返回,但是当传值有深拷贝时会加大内存和计算的开销——解决方法:增加移动构造。当函数需要返回一个对象的时候(返回对象是一个右值),会优先调用对象的移动构造。
-
临时对象维护一块堆上的空间,当临时对象析构后堆上的空间也要释放,移动构造保证了临时对象释放前,将资源交换给新的对象,避免了堆空间的重复申请和释放。
-
对象构造时调用拷贝构造还是移动构造,关键看被拷贝的对象是一个左值还是右值,左值调用拷贝构造;右值调用移动构造。
-
std::move将一个左值move成右值。
实例代码:
class Value
{
public:
Value(const size_t size = 10)
:_size(size)
, _p(new int[_size] {66})
{
std::cout << "Value Constructor->" << this << "____" << this->_p;
if (this->_p)
printf("_____%d", *(int*)(this->_p));
std::cout << std::endl;
}
Value(const Value& v)
{
int size = v.GetDataSize();
//int size = v._size; //这里有个小问题,为什么private的成员变量可以被调用?
std::cout << size << std::endl;
//需要写深拷贝,否则浅拷贝的p指向同一块地址空间,会被析构两次
_p = new int[v._size]{33};
std::cout << "Value Copy Constructor->" << this << "____" << this->_p;
if(this->_p)
printf("_____%d", *(int*)(this->_p));
std::cout << std::endl;
}
Value& operator=(const Value& v)
{
_size = 0;
_p = nullptr;
std::cout << "赋值运算符重载" << std::endl;
Value temp(v);
Swap(temp);
return *this;
}
//移动赋值
Value& operator=(Value&& v)
{
_size = 0;
_p = nullptr;
std::cout << "移动赋值" << std::endl;
Swap(v);
return *this;
}
//移动构造
Value(Value&& v)
:_size(0)
,_p(nullptr)
{
Swap(v); //将右值引用的新空间换给类对象,实现计算的资源的缩减
std::cout << "移动构造:";
std::cout << "Value Copy Constructor->" << this << "____" << this->_p;
if (this->_p)
printf("_____%d", *(int*)(this->_p));
std::cout << std::endl;
}
~Value()
{
std::cout << "Value Deconstructor->" << this << "____" << this->_p;
if (this->_p)
printf("_____%d", *(int*)(this->_p));
if (_p)
{
delete[] _p;
_p = nullptr;
}
std::cout << std::endl;
}
size_t GetDataSize() const
{
return _size;
}
private:
void Swap(Value& v)
{
swap (_p, v._p);
swap(_size, v._size);
}
private:
size_t _size;
int* _p;
};
Value func()
{
Value v;
return v; // 返回对象是一个左值,调用拷贝构造一个临时对象;临时对象是要给右值,给新对象赋值时调用移动构造
//编译器优化后会直接move(v),调用移动构造:v出了作用域会被析构,它的资源可以利用。
}
void SetValue(const Value& v)
{
std::cout << "##### SetValue(const Value& v) #####" << std::endl;
}
void SetValue(Value&& v)
{
std::cout << "##### 右值引用 #####" << std::endl;
}
int main()
{
//Value v = func(); //func()函数返回了一个Value的对象
//SetValue(v);
//std::cout << std::endl;
//SetValue(func());
Value v1;
Value v2 = std::move(v1); //左值move后变右值,移动构造后原本不是将亡值的对象(v1)会被改变,造成调用出错。
//Value v3;
//v3 = v2;
Value v4;
v4 = std::move(v2);
return 0;
}
3. 完美转发
- 使用std::forward让一个右值保留原来的属性,能够传递给一个右值引用的函数。这样右值传参时就可以调用移动语义节省计算资源。
templatevoid Fun(T&t) { std::cout << "T&t" << std::endl; } template void Fun(T&& t) { std::cout << "T&&t" << std::endl; } template void Forward(T&& t) //模板的T&&称为万能引用 { //Fun(std::forward (t)); Fun(t); //若不用forward,则t被右值引用后是一个左值,会调用左值引用 } int main() { // 左值 a、b、c、p、*p int a = 1, b = 2; const int c = 3; int* p = &a; Forward(a); Forward(c); Forward(*p); //右值 1、a+b、add(a, b) Forward(1); Forward(add(a, b)); Forward(a + b); return 0; }
结果显示:
使用了std::forward
T&t T&t T&t T&&t T&&t T&&t
不使用的结果:
T&t T&t T&t T&t T&t T&t参考资料
右值和右值引用
移动构造



