所谓移动语义,指的就是以移动而非深拷贝的方式初始化含有指针成员的类对象。简单的理解,移动语义指的就是将其他对象(通常是临时对象)拥有的内存资源“移为已用”。

事实上,对于程序执行过程中产生的临时对象,往往只用于传递数据(没有其它的用处),并且会很快会被销毁。因此在使用临时对象初始化新对象时,我们可以将其包含的指针成员指向的内存资源直接移给新对象所有,无需再新拷贝一份,这大大提高了初始化的执行效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
using namespace std;

class demo{
public:
demo() : num(new int(0))
{
cout << "construct!" << endl;
}

// 拷贝构造函数(拷贝语义)
demo(const demo &d) : num(new int(*d.num))
{
cout << "copy construct!" << endl;
}

// 添加移动构造函数(移动语义)
demo(demo &&d) : num(d.num)
{
d.num = NULL; // 避免旧类重复释放
cout << "move construct!" << endl;
}

~demo()
{
cout << "class destruct!" << endl;
}

private:
int *num;
};

demo get_demo()
{
return demo();
}

int main()
{
demo a = get_demo();
return 0;
}

移动构造函数使用右值引用形式的参数,num 指针变量采用的是浅拷贝的复制方式,同时在函数内部重置了 d.num,有效避免了“同一块对空间被释放多次”情况的发生。

在 Linux 系统中使用g++ demo.cpp -o demo.exe -std=c++0x -fno-elide-constructors命令执行此程序,输出结果为:

1
2
3
4
5
6
construct!
move construct!
class destruct!
move construct!
class destruct!
class destruct!

注意,目前多数编译器都会对程序中发生的拷贝操作进行优化,因此如果我们使用 VS 2017、codeblocks 等这些编译器运行此程序时,看到的往往是优化后的输出结果:

1
2
3
> construct!
> class destruct!
>

非 const 右值引用只能操作右值,程序执行结果中产生的临时对象(例如函数返回值、lambda 表达式等)既无名称也无法获取其存储地址,所以属于右值。当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。

自定义的拷贝构造函数也会把成员变量拷贝过去,哪怕未显式写上;因此只需要写需要深拷贝的变量即可

如果使用左值初始化同类对象,但也想调用移动构造函数完成,有没有办法可以实现呢?

默认情况下,左值初始化同类对象只能通过拷贝构造函数完成,如果想调用移动构造函数,则必须使用右值进行初始化。C++11 标准中为了满足用户使用左值初始化同类对象时也通过移动构造函数完成的需求,新引入了 std::move() 函数,它可以将左值强制转换成对应的右值,由此便可以使用移动构造函数。

转自:http://c.biancheng.net/view/8694.html