0


【C++11】左值引用和右值引用


目录


一、新的类功能

1、新的默认成员函数

C++11新增了两个类的默认成员函数:移动构造函数和移动赋值运算符重载。

如果你没有手动实现移动构造函数,并且析构函数、拷贝构造、赋值运算符重载均没有手动实现,编译器就会帮你生成一个默认的移动构造函数。默认生成的移动构造函数,对于内置类型进行按字节的值拷贝,对于自定义类型,将会去调用它的移动构造函数,如果这个内置类型并没有移动构造函数,则编译器去调用它的拷贝构造函数。

如果你没有手动实现移动赋值运算符重载,并且析构函数、拷贝构造、赋值运算符重载均没有手动实现,编译器就会帮你生成一个默认的移动赋值运算符重载。默认生成的移动赋值运算符重载,对于内置类型进行按字节的值拷贝,对于自定义类型,将会去调用它的移动赋值运算符重载,如果这个内置类型并没有移动赋值运算符重载,则编译器去调用它的赋值运算符重载。

2、类成员变量初始化

C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化。

3、强制生成默认函数的关键字default

Person(Person&& p)=default;//让编译器强制生成移动构造

4、禁止生成默认函数的关键字delete

例如现在需要禁用某个类的拷贝构造函数,C++98的玩法是将这个类的拷贝构造函数加上私有权限,并且只声明不实现,就可以在类的内部和外部禁用掉拷贝构造。

C++11使用delete关键字禁止生成某个默认构造函数:

A(const A& a)=delete;//防拷贝

二、左值和右值

1、左值和左值引用

变量和解引用得到的变量是左值,我们可以获取它的地址,左值可以出现赋值符号的左边和右边,右值只能出现在赋值符号的右边。

int main()
{
    // 以下的p、b、c、*p都是左值
    int* p = new int(0);
    int b = 1;
    const int c = 2;
    // 以下几个是对上面左值的左值引用
    int*& rp = p;
    int& rb = b;
    const int& rc = c;
    int& pvalue = *p;
    return 0;
}
  1. 左值引用只能引用左值,不能引用右值。

  2. 但是const左值引用既可引用左值,也可引用右值(权限平移)

int main()
{
    // 左值引用只能引用左值,不能引用右值。
    int a = 10;
    int& ra1 = a;   // ra为a的别名
    //int& ra2 = 10;   // 编译失败,因为10是右值
    // const左值引用既可引用左值,也可引用右值。
    const int& ra3 = 10;
    const int& ra4 = a;
    return 0;
}

2、右值和右值引用

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。C++11中将右值划分成纯右值(内置类型表达式的值)和将亡值(自定义类型的表达式的值)

int main()
{
    double x = 1.1, y = 2.2;
    // 以下几个都是常见的右值
    10;
    x + y;
    fmin(x, y);
    // 以下几个都是对右值的右值引用
    int&& rr1 = 10;
    double&& rr2 = x + y;
    double&& rr3 = fmin(x, y);
    // 这里编译会报错:error C2106: “=”: 左操作数必须为左值
    10 = 1;
    x + y = 1;
    fmin(x, y) = 1;
    return 0;
}
  1. 右值引用只能引用右值,不能引用左值。

  2. 但是右值引用可以引用move以后的左值。

int main()
{
     // 右值引用只能右值,不能引用左值。
     int&& r1 = 10;
     
     // error C2440: “初始化”: 无法从“int”转换为“int &&”
     // message : 无法将左值绑定到右值引用
     int a = 10;
     int&& r2 = a;
     // 右值引用可以引用move以后的左值
     int&& r3 = std::move(a);
     return 0;
}

2.1右值的别名是左值

右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1,这里的rr1是左值。如果不想rr1被修改,可以用const int&& rr1 去引用。(这个特性在移动赋值中也可以体现,移动赋值参数是右值引用,通过交换这个右值引用的对象完成赋值的目的)

int main()
{
     double x = 1.1, y = 2.2;
     int&& rr1 = 10;//rr1其实是一个左值
     const double&& rr2 = x + y;
     rr1 = 20;
     rr2 = 5.5;//报错  
     return 0;
}

因为右值引用的形参是左值,层层传递时,需要将这个形参move为右值:

void push_back(T&& x)//x为左值
{
    insert(end(),move(x));//需要用move转换为右值,不然会去调用左值版本的insert
}    
iterator insert(interator pos,T&& x)//x为左值
{
    node* newnode=new node(move(x));//需要用move转换为右值,不然会去调用左值版本的node构造函数
}
list_node(T&& x)//x是左值
    :_next(nullptr)
    ,_prev(nullptr)
    ,_data(move(x))//需要用move转换为右值,不然会去调用左值版本的T _data的构造函数
{}

不过这样写的话需要左值写一份,右值的写一份,代码重复了。更推荐下面讲的完美转发的写法。

2.2模板的万能引用

模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用到完美转发。

2.3完美转发

std::forward 完美转发在传参的过程中保留对象原生类型属性,当然完美转发也是需要在模板的环境下才能生效,如果不是模板,T&& t同样会被识别成右值引用。

万能引用和完美转发必须保证传参时,才实例化对象,如果传参前模板已经被实例化了,将构不成万能引用和完美转发。

使用完美转发修改本节2.1中的move写法:凡是需要往下一层传参的,需要保持右值属性的都需要使用完美转发。

template <class T>
void push_back(T&& x)//x可以接收任意类型
{
    insert(end(),std::forward<T>(x));
}    
iterator insert(interator pos,T&& x)///x可以接收任意类型
{
    node* newnode=new node(std::forward<T>(x));
}
list_node(T&& x)//x可以接收任意类型
    :_next(nullptr)
    ,_prev(nullptr)
    ,_data(std::forward<T>(x))
{}

3、左值引用和右值引用的使用场景和意义

3.1左值引用的使用场景

左值引用在函数传参、函数传返回值(必须保证返回值出了作用域还在)时使用可以减少拷贝。如果函数的返回值出了作用域会被销毁,则不能使用左值引用来减少拷贝(销毁后引用的对象非法),这个时候就需要使用右值引用了。C++在没有右值引用的时候,可以采用输出型参数的方法解决传值返回多次拷贝的问题。

3.2右值引用的使用场景

使用场景一:深拷贝的类中的传值返回的拷贝问题

C++11通过右值引用实现的移动构造和移动赋值解决值返回多次拷贝的问题,提升了效率:

class string
{
public:
    //拷贝构造
    string(const string& s)//传入的s是左值,需要老老实实构造
    {
        string tmp(s.c_str());
        swap(tmp);
    }
    //移动构造
    string(string&& s)//传入的s是将亡值,可以换走s的成员
    {
        swap(s);//将s的成员直接换给*this,s消亡后自动调用析构函数
    }
    // 赋值重载
    string& operator=(const string& s)
    {
        string tmp(s);
        swap(tmp);
        return *this;
    }
    // 移动赋值
    string& operator=(string&& s)
    {
        swap(s);
        return *this;
    }
private:
    char* _str = nullptr;
    size_t _capacity = 0;
    size_t _size = 0;
};

对于深拷贝的类,C++11使用右值引用实现的移动构造和移动赋值,在需要拷贝的场景中,可以直接转移将亡值的资源。如下图,虽然to_string是传值返回,但是str被编译器识别为将亡值,编译器会调用移动构造一步到位的对ret1进行构造,代价很低。

使用场景二:插入右值数据,也可以减少拷贝

int main()
{
    list<string> it;
    string s1("1111");
    it.push_back(s1);//左值,深拷贝
    it.push_back(string("2222"));//右值,直接移动构造转移右值资源
    it.push_back("3333");//右值,直接移动构造转移右值资源
    return 0;
}

三、《Git从入门到精通》

【内容简介】

    Git是一款让人一开始觉得很容易学,但却很难精通的工具。本书除了介绍Git的相关知识外,还会模拟各种常见的状况,让读者知道应该在什么时候使用什么指令。
   《Git从入门到精通》共分11个章节,1~3章介绍安装工具及环境,对于已经安装完成的读者可直接从第4章开始阅读。第5章介绍Git基本的使用方式,虽然难度不高,但却是整个Git系统的基础。第6章介绍Git中常用的分支功能以及使用情境,第7~9章则是介绍如何修改现有的历史记录、使用标签,以及如何应对其他常见的状况。
     前面的内容都是在自己的计算机上就可以完成的,从第10章开始介绍如何将自己计算机里的记录推一份到线上(GitHub)。*后一章(第11章)介绍团队开发时可能会使用的开发过程Git Flow。
     市面上的参考书籍或网络教程大多是教大家如何通过终端机指令来学习Git,这让不少想学习Git的新手打了退堂鼓。本书除了教大家如何在终端机视窗中输入Git指令,还搭配了图形界面工具,缓和了读者的学习曲线,让读者更容易上手。

京东自营购买链接:

《Git从入门到精通》(高见龙)【摘要 书评 试读】- 京东图书

送书活动
本期书籍为《Git从入门到精通》送一本
开奖时间:2023-04-09-23:59
参与方式:
1.点赞收藏文章
2.在评论区留言:人生苦短,我用Git(留言最多五条)
3.抽奖方式为评论区留言随机抽奖(C语言rand()函数随机抽取),会在评论区如期公布中奖者,包邮到家。

🍓 获奖名单🍓

zerodreamer(已私信,因三日未回复故重抽)

EnticE152(重抽中奖)

标签: c++ 开发语言 C++11

本文转载自: https://blog.csdn.net/gfdxx/article/details/130001994
版权归原作者 蒋灵瑜的笔记本 所有, 如有侵权,请联系我们删除。

“【C++11】左值引用和右值引用”的评论:

还没有评论