C++ 移动构造与移动赋值运算符

一,左值与右值

1,左值与左值引用

左值是一个表示数据的表达式,程序可以获取其地址。左值可以出现在赋值语句的左边,也可以出现在赋值语句的右边。左值引用就是对左值的引用。下面的变量都是左值

int a = 20;
const int b = 10;
int c = b;


2,右值与右值引用

右值即可出现在赋值表达式右边,但不能获取其地址。右值包括字面常量(C风格字符串除外,它表示的是地址),诸如x + y表达式以及返回值的函数(条件是该函数返回的不是引用)。C++11新增了右值引用,这是使用&&表示的,右值引用就是对右值的引用。下面的是右值的例子

int getValue(){
    return 50;
}

int main(){
    int x = 10;
    int y = 20;
    int &&r1 = 30;  //字面常量是右值
    int &&r2 = x + y;  //表达式是右值
    int &&r3 = getValue();  //函数返回的是int类型的值
    return 0;
}


二,为何需要移动语义?

我们定义了下面这个Useless类

class Useless{
private:
    string name;
    char *p;
public:
    Useless();
    Useless(string name);
    Useless(const Useless &f);
    Useless operator+(const Useless &f) const;
    void showData();
    ~Useless();
};

定义一个函数这个函数的返回值是Useless类对象

Useless getObject(){
    Useless temp("temp");
    return temp;
}

假设有下面这些代码

Useless three(getObject());

getObject()函数创建temp对象,temp对象管理着10000个字符。Useless的复制构造函数将创建这10000个字符的副本,然后删除getObject()函数返回的临时对象。这里的要点是,做了大量的无用功。考虑到临时对象被删除了,如果编译器将临时对象对数据的所有权直接转让给three对象,不是更好么?也就是说,不将临时对象管理的10000个字符复制到新的地方,再删除临时对象管理的字符串。而是将字符留在原来的地方,并将three对象与之相关联。这类似于在计算机中移动文件的情形,实际的文件还是留在原来的地方,而只修改记录。这种方法被称为移动语义。有点悖论的是,移动语义实际上避免了移动原始数据,而只是修改了记录。


三,使用移动构造函数实现移动语义

使用前面介绍的Useless类,这个类里面的很多函数都很常规,在这里只写出移动复制构造函数的定义。

class Useless{
private:
    string name;
    char *p;
public:
    Useless();
    Useless(string name);
    Useless(const Useless &f);
    Useless(Useless &&f);
    Useless operator+(const Useless &f) const;
    void showData();
    ~Useless();
};

移动复制构造函数

Useless::Useless(Useless &&f):name(f.name){
    p = f.p;
    f.p = NULL;
    showData();
}

应用实例

int main(){
    Useless one("one");
    Useless two("two");
    Useless three(one + two);
    return 0;
}

输出结果

one Object Data Address : 0xcc0d78
two Object Data Address : 0xcc0da8
one+two Object Data Address : 0xcc0e08
one+two Object Data Address : 0xcc0e08
one+two Object delete. 0x000000
one+two Object delete. 0xcc0e08
two Object delete. 0xcc0da8
one Object delete. 0xcc0d78

Process returned 0 (0x0) execution time : 0.012 s
Press any key to continue.

程序分析
在方法operator+中创建的对象的数据地址与对象three存储的数据地址相同(都是 0xcc0e08),其中对象three是由移动复制构造函数创建的。另外,注意到创建对象three之后,为临时对象调用了析构函数。之所以知道这是临时对象,是因为其数据地址是: 0x000000。使用移动复制构造函数直接交换了临时对象与three对象之间的数据的所有权。


四,移动构造函数解析
1,移动构造函数

Useless::Useless(Useless &&f):name(f.name){
    p = f.p;
    f.p = NULL;
    showData();
}

上面的代码使p直接指向现有的数据,以获取这些数据的所有权。此时,p与f.p指向相同的数据,调用析构函数时,这将带来麻烦,因为程序不能为同一个地址调用delete []两次。为避免这个问题,该构造函数随后将原来的指针设置为空指针,因为对空指针执行delete []没有问题。
注意:

由于修改了f对象,这要求不能在参数声明中使用const。


2,移动构造函数解析
要让移动语义发生,需要下面两个步骤。
(1),右值引用让编译器知道何时可使用移动语义

Useless one("one");
Useless two("two");
Useless three(one + two);

对象one与two是左值,与左值引用匹配,而表达式one + two是右值,与右值引用匹配。因此,右值引用让编译器使用移动复制构造函数初始化对象three。

 

(2),定义移动复制构造函数。


五,移动构造函数与移动赋值运算符

1,C++11在原有4个特殊成员函数的基础上,新增了两个:移动构造函数与移动赋值运算符。默认的移动构造函数与移动赋值运算符与复制版本类似,执行逐成员初始化并复制内置类型。如果成员是类对象,将使用相应类的构造函数和赋值运算符,就像参数为右值一样。如果定义了移动构造函数与移动赋值运算符,这将调用他们。否则将调用复制构造函数和赋值运算符。


2,如果你提供了析构函数、复制构造函数或赋值运算符,编译器将不会自动提供移动构造函数和移动赋值运算符。如果你提供了移动构造函数或移动赋值运算符,编译器将不会自动提供复制构造函数和赋值运算符。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值