移动语义笔记

左值与右值区别
右值引用、对象移动 在c++中,一个值要么是右值,要么是左值,左值是指表达式结束后依然存在的持久化对象,右值是指表达式结束时就不再存在的临时对象。所有的具名变量或者对象都是左值,而右值不具名。 C引入右值引用之后,可以通过右值引用,充分使用临时变量减少不必要的拷贝,提高效率。

int&& i = 123;
int&& j = std::move(i);
int&& k = i;//编译不过,这里i是一个左值,右值引用只能引用右值
右值引用不能绑定到左值上;左值引用(常规引用)不可以绑定到右值,但const &是可以的
int const & kk = 123;//正确
//以下都是正确的
int f();
vector<int> vi(100);
int&& r1 = f();
int& r2 = vi[0];
int& r3 = r1;
int&& r4 = vi[0] * f();

移动构造函数和移动运算符

移动构造:
StrVec::StrVec(StrVec &&s) noexcept : elements(s.elements), first_free(s.first_free), cap(s.cap)
//移动构造必须不抛出异常,防止数据出问题
{
    // 赋空指针保证移后源对象必须可析构.
    s.elements = s.first_free = s.cap = nullptr;
}
移动运算符:
StrVec& StrVec::operator = (StrVec &&rhs) noexcept
{
    if (this != &rhs) {//自检查
        free();
        elements = rhs.elements;
        first_free = rhs.first_free;
        cap = rhs.cap;
        rhs.elements = rhs.first_free = rhs.cap = nullptr;
    }
    return *this;
}

转发
万能引用:参数既可以表示左值引用,也可以表示右值引用,但只发生类型推导的时候,T&&才表示万能引用;否则,表示右值引用

template<typename T>
//万能引用(因为有模板),既可以接收左值也可以接收右值
void func(T&& param) {
    cout << param << endl;
}
int main() {
    int num = 2019;
    func(num);//参数是左值
    func(2019);//参数是右值,两者匹配的同一个函数
    return 0;
}

引用折叠(Reference Collapse)
万能引用说完了,接着来聊引用折叠(Reference Collapse),因为完美转发(Perfect Forwarding)的概念涉及引用折叠。一个模板函数,根据定义的形参和传入的实参的类型,我们可以有下面四中组合:

左值-左值 T& & # 函数定义的形参类型是左值引用,传入的实参是左值引用
左值-右值 T& && # 函数定义的形参类型是左值引用,传入的实参是右值引用
右值-左值 T&& & # 函数定义的形参类型是右值引用,传入的实参是左值引用
右值-右值 T&& && # 函数定义的形参类型是右值引用,传入的实参是右值引用
但是C++中不允许对引用再进行引用,对于上述情况的处理有如下的规则:

所有的折叠引用最终都代表一个引用,要么是左值引用,要么是右值引用。规则是:如果任一引用为左值引用,则结果为左值引用。否则(即两个都是右值引用),结果为右值引用。

即就是前面三种情况代表的都是左值引用,而第四种代表的右值引用。

完美转发(Perfect Forwarding)
下面接着说完美转发(Perfect Forwarding),首先,看一个例子:

#include <iostream>
using std::cout;
using std::endl;
template<typename T>
void func(T& param) {
    cout << "传入的是左值" << endl;
}
template<typename T>
void func(T&& param) {
    cout << "传入的是右值" << endl;
}
template<typename T>
void warp(T&& param) {
    func(param);
}
int main() {
    int num = 2019;
    warp(num);
    warp(2019);
    return 0;
}

猜一下,上面的输出结果是什么?

传入的是左值
传入的是左值

是不是和我们预期的不一样,下面我们来分析一下原因:
warp()函数本身的形参是一个万能引用,即可以接受左值又可以接受右值;第一个warp()函数调用实参是左值,所以,warp()函数中调用func()中传入的参数也应该是左值;
第二个warp()函数调用实参是右值,根据上面所说的引用折叠规则,warp()函数接收的参数类型是右值引用,那么为什么却调用了调用func()的左值版本了呢?这是因为在warp()函数内部,右值引用类型变为了左值,这个param变量永远是左值,因为参数有了名称,我们也通过变量名取得变量地址。
相当于
T && param=2019//param是左值,表达式是右值引用

那么问题来了,怎么保持函数调用过程中,变量类型的不变呢?这就是我们所谓的“完美转发”技术,在C++11中通过std::forward()函数来实现。我们修改我们的warp()函数如下:

template<typename T>
void warp(T&& param) {
    func(std::forward<T>(param));
}

则可以输出预期的结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值