[cpp32 note] 3. 右值和移动

左值、右值:

在这里插入图片描述
我们暂时只关注lvalue和prvalue
左值lvalue:有标识符,可以取地址的表达式
纯右值prvalue:没有标识符,不可以取地址的表达式(也叫临时对象)。

类型是右值应用的变量,是一个左值,例如:

template<typename S>
smart_ptr(const smart_ptr<S>&& other) const {
}
// 这里的other是一个右值引用,但它是左值
std::move
smart_ptr<S> ptr = std::move(ptr1)

这里的ptr1是左值引用,std::move的作用是将左值引用转变为右值引用,std::move(ptr1)的结果是指向ptr1的一个右值引用。

而std::move(ptr1)这个表达式可以看作是一个有名字的右值,称为 xvalue。
xvalue有名字(这一点和lvalue类似),但是xvalue不可以取地址,因此xvalue和prvalue一样,都归为右值。

生命周期延长:

为了方便对临时对象的使用,C++ 对临时对象有生命周期延长规则:
当prvalue被绑定引用上,它的生命周期将会和这个引用变量一样。
(需要注意的是,生命周期延长规则只对prvalue有效,对xvalue无效)

生命周期延长规则,对之后要学习的视图(view)部分用处很大。

移动的意义:

c++的对象默认是值语义,需要移动来减少性能开销(特别是返回大对象的函数和运算符)
移动可以减少不必要的拷贝开销。(例如堆对象的swap只会交换对象指针?)

所有现代cpp容器都对移动进行了优化。

如何实现移动:
  1. 要移动的对象有分开的拷贝构造和移动构造函数(如果不需要支持拷贝,可以没有拷贝构造)
  2. 要移动的对象有swap成员函数,用于和另外一个对象快速交换成员。即A.swap(B)
  3. 要移动的对象命名空间下应该有一个全局的swap函数。即swap(A,B)
  4. 实现赋值操作符 =
  5. 上述函数若不抛出异常,应标记为noexpect,特别是移动构造函数
不要返回本地变量的引用:

返回本地变量的引用是未定义行为。

cpp11之前,返回本地变量会发生拷贝,但是编译器会做返回值优化(NRVO, named return value optimization),将对象直接构造到调用者的栈上。

cpp11之后,返回值优化仍然存在,但在没有返回值优化的情况下,会尝试将变量移动出去而不是拷贝出去。程序员不需要显式std::move,否则反而会影响NRVO(本来可以直接构造到调用者的栈上,结果变成了移动构造)。

有分支时一般没有NRVO,例如:

A func(int n) {
	A a;
	A.b;
	if (b&1)return a;
	else return b;
}
// 没有NRVO,return时会尝试移动出去
引用坍缩(引用折叠):

引用坍缩的概念在泛型编程中一定会遇到。

T& 一定是一个左值引用。
但是T&&既可能是左值引用,也可能是右值引用。因为右值引用

对于

template foo(T&&)

如果传入的参数是左值,那么T的推导结果是左值引用;
如果传入的参数是右值,那么T的推到结果是类型本身。
如果T是左值引用,那么T&&的结果仍是左值引用,即(type&)&&坍缩成了type&
如果T是实际类型,那么T&&的结果就正常的是右值引用。

右值引用变量,实际上是一个左值,因此会匹配到左值引用上。

完美转发:

对于以下代码:

void foo(const shape& s){}
void foo(shape&& s) {}
void bar(shape&& s){
	foo(s);
}
void main(){
	bar(circle());
}

会先调用void bar(shape&& s)
然后调用void foo(const shape& s)
因为s虽然是右值引用,但s是左值,
所以在bar(shape&& s)中调用foo(s)时,会将s匹配到左值引用上。

如果我们想要让foo(s)调用void foo(shape&& s),我们需要将代码修改为:

void bar(shape&& s){
	foo(std::move(s));
}

这意味着我们必须另写一份基本一样的bar函数, 仅仅只有std::move(s)的区别。

有没有办法能够在保持参数的值类别呢,即s

很多标准库里的函数,连目标的参数类型都不知道,但仍然能保持参数的值类别:左值的仍然是左值,右值的仍然是右值。
C++标准库中提供这个功能 ,即std::forward。

只需要将bar函数修改成这样:

template<typename T>
void bar(T&& s)
	foo(std::forward<T>s);
}

如果传入的是左值,那么就会调用左值引用的foo()
如果传入的是右值,那么就会调用右值引用的foo()

void bar(T&& s)中,T&&的作用主要是保持值类别进行转发,也叫转发引用(forwarding reference)。因为它既可以接收左值引用,也可以接收右值引用,也被称为万能引用(universal reference)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值