C++之右值引用、移动构造函数

右值

  1. 左值

左值是指放在赋值号左边可以被赋值的值(左值必须要在内存中有实体)

  1. 右值

右值是指放在赋值号右边赋给其他变量的值(右值可以在内存也可以在CPU寄存器)

右值不可以直接修改
右值可以赋值给左值
右值不可以直接取地址

C++11将右值分为纯右值将亡值

纯右值: 非引用返回的临时变量;运算表达式产生的结果;字面常量.
将亡值:其实就是中间变量的过渡,过度后就消亡,分为两种
1. 函数的临时返回值int a = f(3);f(3) 的返回值就是右值,副本拷贝给a,然后消亡
2. 表达式像(x+y),其中(x+y)就是右值

这里有个问题,右值不可以修改,那如果我们想要修改右值怎么办?解决方法就是右值引用

右值引用

  1. 左值引用

类型名 &引用名 = 左值表达式;

  1. 右值引用(就是给右值取个名字,因为右值是匿名的,所以我们要找到它必须用引用的方式,也就是给右值起一个名称)

类型名 && 引用名 = 右值表达式;

int a1 = 10;
//int&& a2 = a1;    //右值引用无法绑定到左值上
//注释:getObj返回一个Object对象
//Object& t = getObj();    //右值引用无法初始化左值引用

下面给出示例1

#include<iostream>
using namespace std;
class AA
{
public:
    int m_a = 9;
    
};
AA getTemp()
{
    return AA();
}
int main()
{
    int&& a = 3;    //3是右值
    a++;
    int b = 8;        //b是左值
    int&& c = b + 5;    //b+5是右值
    c++;
    AA&& aa = getTemp();    //返回值是右值
    //getTemp的返回值的生命周期在getTemp函数结束时就该结束,
    //但是通过右值能给右值“续命”
    //左值引用只能绑定左值,右值引用只能绑定右值
    //常量左值引用可以绑定非常量左值、常量左值、右值,
    //而且常量左值引用还可以像右值的生命期延长,缺点:只能读不能改

    aa.m_a++;
    cout << "a=" << a << " " << &a << endl;

    cout << "c=" << c << " " << &c << endl;
    cout << "aa.m_a=" << aa.m_a << " " << &aa.m_a << endl;

    return 0;
}
getTemp的返回值的生命周期在getTemp函数结束时就该结束,但是通过右值能给右值“续命”,此时他的右值生命周期和aa的声明周期一样。
左值引用只能绑定左值,右值引用只能绑定右值,常量左值引用可以绑定非常量左值、常量左值、右值,而且常量左值引用还可以像右值的生命期延长,缺点:只能读不能改

示例2

#include<iostream>
using namespace std;
class Object
{
public:
    Object():m_num(new int(100)) 
    {
        cout << "Object()" << endl;
    }
    ~Object()
    {
        delete m_num;
        cout << "~Object()" << endl;
    }
    int* m_num;
};
void test1()
{
    int a = 50;    //a->左值,50->右值
    //左值右值引用都必须立即初始化,通过右值引用的声明,该右值获得重生。
    //该右值的生命周期与该右值引用变量的生命周期一样
    int a1 = 10;
    //int&& a2 = a1;    //右值引用无法绑定到左值上
    //Object& t = getObj();    //右值引用无法初始化左值引用
    Object&& t1 = getObj();
    const Object& t2 = getObj();
    //常量左值引用是一个万能引用类型,它可以接受左值、右值、常量左值和常量右值。
}

右值引用的应用(移动构造函数)

在C++中进行对象赋值时,有时候会发生对象的深拷贝,如果对象的堆内存太大,拷贝的代价是很大的,所以程序性能会降低,如果想要避免深拷贝,可以使用右值引用

看一个示例

#include<iostream>
using namespace std;
class Object
{
public:
    Object():m_num(new int(100)) 
    {
        cout << "Object()" << endl;
    }
    Object(const Object& a) :m_num(new int(*a.m_num))
    {
        cout << "Object(&)" << endl;
    }
    //移动拷贝构造函数
    /*Object(Object&& a) :m_num(a.m_num)
    {
        a.m_num = nullptr;
        cout << "Object(&&)" << endl;
    }*/
    ~Object()
    {
        delete m_num;
        cout << "~Object()" << endl;
    }
    int* m_num;
};
Object getObj() 
{ 
    Object tmp;
    return tmp; 
}
void test2()
{
    Object obj2 = getObj();
}
void main()
{
    test2();
}

我们发现调用了深拷贝构造函数,我们把代码注释的部分打开运行结果如下

我们发现调用了移动构造函数(参数为右值引用类型),这有什么意义呢???我们发现移动构造函数只是对对象进行了浅拷贝,性能优化。因为赋值号右边是一个函数的返回值,在函数的栈帧中创建的临时对象在函数结束时就会释放,这时移动构造函数用了右值引用,将函数栈帧中创建的临时对象中的堆内存的(资源)所有权交给了tmp,这块内存得到了“续命”。

&&的扩展

根据前面说的,是否&&就代表一个右值引用。答案是否定的。

具体上体现在模板和自动类型推导。

当模板参数是T&&,或者自动类型推到指定为auto &&时,这时&&称为未定的引用类型。
注意:const T&&表示右值引用
通过右值推导 T&& 或者 auto&& 得到的是一个右值引用类型,其余都是左值引用类型。

函数模板中的&&

练习

#include<iostream>
using namespace std;
template<typename T>
void f(T&& param) {}
void f1(const T&& param) {}
void main()
{
    f(10);    //实参->右值,T&&->右值引用
    int a=10;    
    f(a);    //a->左值,T&&->左值引用
    //f1(a);    //错误,a->左值,constT&&本身时右值引用,左值不能初始化右值引用
    f1(10);    //实参->右值
}

自动类型推导的&&

练习

void test5()
{
    int&& a1 = 5;    
    auto&& bb = a1;    //a1->右值引用,bb->左值引用
    auto&& bb1 = 5;    //5->右值,bb1->右值引用
    int a2 = 5;    
    int& a3 = a2;    
    auto&& cc = a3;    //a3->左值引用,auto&&->左值引用
    auto&& cc1 = a2;    //a2->左值,auto&&->左值引用
    const int& s1 = 100;    
    const int&& s2 = 100;    
    auto&& dd = s1;    //s1->常量左值引用,dd->常量左值引用
    auto&& ee = s2;    //s2->常量右值引用,ee->常量左值引用
    const auto&& x = 5;
}

最后做一个题目,写出运行结果

#include<iostream>
using namespace std;
void Print(int& value)
{
    cout << "l_value:" << value << endl;
}
void Print(int&& value)
{
    cout << "r_value:" << value << endl;
}
void forward(int&& x)
{
    Print(x);
}
void test()
{
    int i = 666;
    Print(i);
    Print(6);
    int&& l_val = 6;
    Print(l_val);
    forward(369);
}
void main()
{
    test();
}

解析

Print(i); i->左值,调用重载的Print(int& value);
Print(6); 6->右值,调用重载的Print(int&& value);
Print(l_val); l_val->左值,调用重载的Print(int& value);
forward(369); 函数接受的是右值,在函数中有调用Print,把参数当成左值处理;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小谢%同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值