C++11之右值引用,move与forward

《深入理解C++11》笔记–右值引用:移动语义和完美转发_WizardtoH-CSDN博客_右值引用和移动语义

临时变量=不具名变量=没有名字的变量。但凡是有名字的变量,都称为左值。因此:
右值引用是对纯右值(临时变量)的引用,而右值引用本身是一个左值,他的使用方法和普通的左值变量完全相同。除了右值引用,const引用也可以对临时变量引用


当一个纯右值被引用后,该值就变成了一个右值。即如果int&&a=0,那么0就从一个纯右值变成了一个右值/将亡值。a本身是一个左值,它引用的值是一个右值。故当a如果作为实参传递时,匹配到的形参会是一个左值引用

右值引用与左值引用的汇编代码基本一样的,除了对于右值需要先把等号右边的值隐式地赋值给一个变量中,而对于左值引用等号右边已经存在该变量.由于引用是指针的语法糖,故引用都是可以再次被赋值的

move的作用其实就是对于任意入参,都能返回一个右值。move本身没有实现move,在下文中可以发现,是通过Move实现的移动构造函数中通过代码实现了真正的move。move返回右值如下:

int a = 2;
int && b1 = a;//错误,右值不能赋值给左值
int && b2 = std::move(a); //正确

右值引用实际就和const左值引用是基本一样的,唯一不同的是右值引用可以修改,而const左值引用只读。以下例子同时证明了当右值引用作为实参传递给形参时,匹配到的会是左值引用!

#include <stdio.h>
#include <string>
struct test
{
    test() = default;
    test(test &t)
    {
        printf("copy\n");
    }
    test(const test &t)
    {
        printf("const copy\n");
    }
    test(test && t)
    {
        printf("move\n");
        t.t=2;
    }
    int t = 0;
};

test generate(std::string s)
{
    test a,b;
    if (s=="")
    {
        return a;
    }
    else
    {
        return b;
    }
}
int main()
{
    test t = generate("");
    test && tt = std::move(t); //move,如果没有定义move的构造函数,则会调用const引用的构造函数
    tt.t=4;
    printf ("t=%d\n", t.t); // 4
    test ttt = tt; //copy!!!
    test tttt = std::move(t); //move
    return 0;
}
//由于右值引用本身是一个左值,故无法实现RTO时,还是需要通过返回move来返回右值引用

class Matrix
{
 ...
 void operater+= (const Matrix &m) const;
};

Matrix Add(Matrix &&a, Matrix &&b)
{
    a += b;
    return std::move(a);
}

-为什么要诞生右值引用而不直接使用const左值引用?

1.个人理解,是为了更加直观。因为const左值引用知识恰好可以引用右值,但其设计理念并不是为了引用右值而诞生。在c++11中也引入了cosntexpr来取代const中常量的概念,让const应该只用于只读变量。

2.const左值引用无法修改,而右值引用可以。这一点对于涉及指针的构造函数时尤其重要。因为临时变量在返回后就会被析构,如果使用临时变量来初始化,因为const引用不可改,故初始化时还是需要深拷贝,不然临时变量析构时会把内存析构掉。但对于右值引用,可以让临时变量的指针为空,就能实现转移的目的:

class Vector {
public:
  Vector(int len, int initial_value) : len_(len) {
    elements_ = new int[len];
    memset(elements_, initial_value, len * sizeof(int));
  }

  Vector(const Vector& v) : len_(v.len_) {
    elements_ = new int[len];
    memcpy(elements_, v.elements_, len * sizeof(int));
  }

  Vector(Vector && v) : len_(v.len_) {
    elements_ = v.elements_; //转移指针所有权
    v.elements = nullptr; //把右值引用的指针所指对象置为空,避免析构时释放
  }

  ...

private:
  int *elements_;
  int len_;
};

Vector ConstructVector(int len) {
  return Vector(len, 0);
}

Vector v = ConstructVector(1000000);

3.对于std::thread, std::unique_ptr这些无法复制的类型,以前是无法push_back到vector等STL里面的。但是通过move移动类型中的变量就能够实现push_back

一些标准库函数中会对右值引用的参数把其指针修改为指向空。故当对一个变量使用move后,除非是自定义变量,就不应该再使用该变量,该变量的状态将会是未知

forward:完美转发

在了解完美转发前,需要了解C++定义的引用折叠规则:

可以看出,在传参时,只有当实参是右值类型,而声名的传参为原对象类型或者右值类型时,传参实际才会为右值类型,否则都为左值。因此,我们可以自己构造一个中间函数forward_value,来根据传入的实参类型完美转发到对应的实际要调用的函数。其中需要调用到std::forward函数,其作用是根据T的实际类型进行转发。在类模板中,T&&类型称为forward type,未定的引用类型

希望通过移动构造函数初始化类时,如果该类中的成员变量都已经有移动构造函数,那么编译器会构造该类的默认移动构造函数,不需要自己再去定义。

注意,完美转发产生的前提必须是在模板函数中。否则当形参是右值引用时,如果实参传入左值编译器会报错

完美转发的意义其实就是为了当形参为右值引用时,对于左值和右值作为实参都能工作,结构更统一美观。但同时也需要注意识别在函数总参数究竟是一个左值引用还是右值引用

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值