右值引用与移动构造函数

C++ 11引入了对象移动操作,为了支持移动操作,新标准引入了一种新的引用类型:右值引用。
**

1. 右值引用

**

1)所谓右值引用就是必须绑定到右值上的引用。我们使用&&而不是&来获取右值引用。
注:左值 / 右值是表达式的属性。一般而言,一个左值表达式表示一个对象的身份,而一个右值表达式表示的是对象的值。(区分左值还是右值的一个简单方法:看能不能对表达式取地址,如果能则为左值,否则为右值)
另外,无论是左值引用(&)还是右值引用(&&),它们都是一个对象的另一个名字而已。
例如:

int i = 42;    //i是左值
int &r = i;    //正确:r引用i
int &&r = i;   //错误:i是左值,不能将一个右值引用绑定到一个左值上
int &r2 = i * 42;      //错误:i * 42是一个右值
int &&rr2 = i * 42;    //正确:将rr2绑定到乘法结果上
const int &r3 = i * 42;    //正确:可以将一个const引用绑定到右值上
//注:常量左值引用是一个“万能”的引用类型,可以接收左值、右值、常量左值和常量右值。

int &&rr1 = 42;     //正确:字面值常量是右值
int &&rr2 = rr1;    //错误:表达式rr1是左值!
//注:变量表达式是左值,我们不能将一个右值引用绑定到一个变量上,即使这个变量是右值引用类型。

2)左值持久,右值短暂
左值有持久的状态,一般在表达式结束后依然存在;而右值要么是字面值常量,要么是表达式求值过程中创建的临时变量,他们在表达式结束后就会被销毁。
但是,通过右值引用的声明,右值可以“重获新生”,其生命周期与右值引用类型变量的生命周期一样长,只要该变量还存在,该右值临时变量将会一直存活下去。例如:

  int i = getVar();
  int &&k = getVar();
  //第二行语句中getVar()产生的临时对象不会像第一行代码那样,在表达式结束之后就销毁了,而是会
  //被“续命”,它的生命周期将会通过右值引用得以延续,和变量k的生命周期一样长。

3)标准库move()函数
头文件:#include
返回值:给定对象的右值引用
功能:将一个左值转换为对应的右值引用类型(move告诉编译器,我们有一个左值,但我们希望像一个右值一样处理它)

int &&rr3 = std::move(rr1);    //正确:std::move(rr1)返回右值

注意:调用move()后rr1的值是不确定的。我们可以销毁rr1对象,也可以赋予它新值,但不能再使用rr1的值。

2. 移动够造函数

1)在拷贝构造函数中,存在深层复制与浅层复制的问题。
一个带有指针成员的类,必须提供一个深层复制的拷贝构造函数(使用new在堆上重新申请一块空间,并把地址赋给指针成员),因为默认的拷贝构造函数是浅层复制(直接采用赋值运算符,复制指针),而且对于指针来说,浅层复制是非常危险的,因为两个指针共同指向同一片内存空间,若第一个指针被释放,另一个指针就不合法了(指向被释放的空间)。

class A
{
public:
    A():m_ptr(new int(0)){cout << "construct" << endl;}
    A(const A& a):m_ptr(new int(*a.m_ptr)) //深拷贝的拷贝构造函数
    {
        cout << "copy construct" << endl;
    }
    ~A(){ delete m_ptr;}
private:
    int* m_ptr;
};
int main() {
    A a = GetA();
    return 0;
}
    输出:
construct
copy construct
copy construct

提供深层复制的拷贝构造函数虽然可以保证正确,但是会造成额外的性能损耗,相比浅层复制,它需要重新申请堆空间,如果堆空间很大的话,这个拷贝构造的代价会很大。
而且对于这样一种情况,“我们用对象a初始化对象b,之后对象a就不再使用”,深层复制其实并不是必须的。既然拷贝构造函数实际上是把a对象的内容复制一份到b中,那么我们为什么不直接使用a的空间呢,这样就避免了新空间的分配,大大降低了构造成本。
在这里插入图片描述
2)C++引入了移动构造函数,专门处理这种“用a初始化b后,就将a析构”的情况。
与拷贝构造函数不同,移动构造函数不分配任何新内存,它接管给定对象的内存。在接管内存之后,它将给定对象中的指针都设置为nullptr,这样就完成了从给定对象的移动操作。注意:原对象将继续存在,直到调用析构函数。

A(A&& a) :m_ptr(a.m_ptr)
{
    a.m_ptr = nullptr;
    cout << "move construct" << endl;
}

拷贝构造函数中,对于指针,我们一定要采用深层复制,而移动构造函数中,我们采用浅层复制。
类似于拷贝构造函数,移动构造函数的参数是该类类型的一个引用,但是它是一个右值引用
3)如果没有移动构造函数,右值也被拷贝
如果一个类有拷贝构造函数,但未定义移动构造函数。在此情况下,编译器不会合成移动构造函数。此时如果需要参数为右值的构造函数,则编译器会调用相应的拷贝构造函数。

参考:
《C++ Primer》第5版
https://www.cnblogs.com/qicosmos/p/4283455.html
https://www.cnblogs.com/qingergege/p/7607089.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值