C++右值引用

引用的本质是对一个变量起别名;
引用不会导致新的对象产生(即不会调用构造函数、拷贝构造函数、移动构造函数),当被引用的对象析构时,那么这个引用就成为了一个悬垂引用,使用这个悬垂引用,会导致程序产生未定义行为

左值引用与右值引用
1.对左值的引用即为左值引用,对右值的引用即为右值引用

2.const T& 引用 和 右值引用,都具有延长临时对象生命周期的能力,但是注意:这种延长生命周期的能力,不能跨作用域,比如以下示例:

  • 示例1
const int &func()
{
    return 100;
}
const int& ref = func();
//再使用ref时,ref已经变为悬垂引用,因为ref引用的临时对象 100, 已经超出了作用域

const int& ref2 = 100;
//这种对临时变量 100的生命周期进行延长,是ok的
  • 示例2
class Test
{
public:
    Test(int x) : m_x(x)
    {
        qDebug() << "construct" << this ;
    }
    Test (const Test& other) : m_x(other.m_x)
    {
        qDebug() << "copy construct" << this ;
    }
    Test (Test&& other) : m_x(other.m_x)
    {
        qDebug() << "move construct" << this ;
    }
    ~Test()
    {
        qDebug() << "distruct test" << this ;
    }
    
    int m_x = -1;
};

const Test& foo(const Test& t)      //测试临时变量的生命周期
{
    return t;
}

Test& foo2()                        //测试跨作用域的左值引用,生命周期
{
    Test t(10);
    return t;
}

Test&& foo3()                       //测试跨作用域的右值引用生命周期(这种写法非常危险!!!)
{
    Test t(20);
    return std::move(t);
}

Test foo4()							//std::move 会阻止返回值优化 RVO
{
    Test t(30);
    return std::move(t);
}

Test foo5()    //返回值优化 RVO
{
	Test t(30);
	return t;
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
//    const Test& ret = foo(10);	
//首先会通过10,创建一个临时Test对象,然后返这个临时变量的引用,但是这一行结束后,临时变量的生命周期即结束,ret成为悬垂引用, 此处参考:https://www.cnblogs.com/apocelipes/p/18291697  临时变量的生命周期到完整表达式结束

    Test& ret2 = foo2();
    //返回的是 foo2中局部对象t的左值引用,使用ret绑定到这个左值引用,但是引用不能跨作用域,出了函数 foo2之后,t对象即销毁,ret2成为悬垂引用

	Test ret3 = foo2();
	//虽然返回的是 foo2中局部对象t的左值引用,但是不是使用引用的形式接收返回值,所以ret3是使用拷贝构造函数创建的一个全新的对象,出了函数 foo2之后,t对象即销毁,但是并不影响ret3对象
	
    Test&& ret4 = foo3();
       //返回的是 foo3中局部对象t的右值引用,使用ret绑定到这个右值引用,但是引用不能跨作用域,出了函数 foo3之后,t对象即销毁,ret4 成为悬垂引用

	Test ret5 = foo3();
	//首先返回的是 foo3中局部对象t的右值引用,这段代码的本意是希望通过移动构造函数来创建一个全新的ret5对象,但是std::move会阻止编译器的返回值优化;这里无论是使用右值引用的形式接收返回值(ret4),还是使用左值的形式接收返回值(ret5), 都会出现垂悬引用,此处非常危险,不要使用std::move返回局部变量的右值引用,且函数返回值还是 && 类型
	
	Test ret6 = foo5();
	//直接返回局部变量,在现在编译器中,编译器会返回值优化(简称 RVO),直接调用构造函数生成ret6对象
    
    return a.exec();
}

foo3()的写法非常危险,返回局部变量的右值引用,且函数返回值还是 && 类型,会造成垂悬引用

foo4()函数的效率并没有foo5()的效率高,因为foo5()使用了RVO,而foo4()由于std::move无法使用RVO,会多一次移动构造操作; 最推荐的写法还是 foo5(),返璞归真,编译器做了所有优化,程序员的使用负担最小

3.使用左值来构造一个对象,那就是调用的对象的拷贝构造函数;使用右值来构造一个对象,那就是调用的对象的移动构造函数,若该类没有移动构造函数,则调用拷贝构造函数

结论:

一个函数,无论返回的是一个Test对象,还是一个包含Test对象的STL的容器,都应该使用最朴素的方式,直接在函数中返回一个临时变量,并且函数的返回值是最朴素的类型,如果foo5(),就可以利用编译器的RVO来实现最佳效果,程序员无需担心其他

参考1
参考2
参考3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值