Move semantics (Move语义)
这是C++11中所涵盖的另一个重要话题。就这个话题可以写出一系列文章,仅用一个段落来说明显然是不够的。因此在这里我不会过多的深入细节,如果你还不是很熟悉这个话题,我鼓励你去阅读更多地资料。
C++11加入了右值引用(rvalue reference)的概念(用&&标识),用来区分对左值和右值的引用。左值就是一个有名字的对象,而右值则是一个无名对象(临时对象)。move语义允许修改右值(以前右值被看作是不可修改的,等同于const T&类型)。
C++的class或者struct以前都有一些隐含的成员函数:默认构造函数(仅当没有显示定义任何其他构造函数时才存在),拷贝构造函数,析构函数还有拷贝赋值操作符。拷贝构造函数和拷贝赋值操作符提供bit-wise的拷贝(浅拷贝),也就是逐个bit拷贝对象。也就是说,如果你有一个类包含指向其他对象的指针,拷贝时只会拷贝指针的值而不会管指向的对象。在某些情况下这种做法是没问题的,但在很多情况下,实际上你需要的是深拷贝,也就是说你希望拷贝指针所指向的对象。而不是拷贝指针的值。这种情况下,你需要显示地提供拷贝构造函数与拷贝赋值操作符来进行深拷贝。
如果你用来初始化或拷贝的源对象是个右值(临时对象)会怎么样呢?你仍然需要拷贝它的值,但随后很快右值就会被释放。这意味着产生了额外的操作开销,包括原本并不需要的空间分配以及内存拷贝。
现在说说move constructor和move assignment operator。这两个函数接收T&&类型的参数,也就是一个右值。在这种情况下,它们可以修改右值对象,例如“偷走”它们内部指针所指向的对象。举个例子,一个容器的实现(例如vector或者queue)可能包含一个指向元素数组的指针。当用一个临时对象初始化一个对象时,我们不需要分配另一个数组,从临时对象中把值复制过来,然后在临时对象析构时释放它的内存。我们只需要将指向数组内存的指针值复制过来,由此节约了一次内存分配,一次元数组的复制以及后来的内存释放。
以下代码实现了一个简易的buffer。这个buffer有一个成员记录buffer名称(为了便于以下的说明),一个指针(封装在unique_ptr中)指向元素为T类型的数组,还有一个记录数组长度的变量。
|
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
template
<typename
T>class
Buffer { std::string
_name; size_t
_size; std::unique_ptr<T[]>
_buffer;public: //
default constructor Buffer(): _size(16), _buffer(new
T[16]) {} //
constructor Buffer(const
std::string& name, size_t
size): _name(name), _size(size), _buffer(new
T[size]) {} //
copy constructor Buffer(const
Buffer& copy): _name(copy._name), _size(copy._size), _buffer(new
T[copy._size]) { T*
source = copy._buffer.get(); T*
dest = _buffer.get(); std::copy(source,
source + copy._size, dest); } //
copy assignment operator Buffer&
operator=(const
Buffer& copy) { if(this
!= ©) { _name
= copy._name; if(_size
!= copy._size) { _buffer
= nullptr; _size
= copy._size; _buffer
= _size > 0 > new
T[_size] : nullptr; } T*
source = copy._buffer.get(); T*
dest = _buffer.get(); std::copy(source,
source + copy._size, dest); } return
*this; } //
move constructor Buffer(Buffer&&
temp): _name(std::move(temp._name)), _size(temp._size), _buffer(std::move(temp._buffer)) { temp._buffer
= nullptr; temp._size
= 0; } //
move assignment operator Buffer&
operator=(Buffer&& temp) { assert(this
!= &temp); //
assert if this is not a temporary _buffer
= nullptr; _size
= temp._size; _buffer
= std::move(temp._buffer); _name
= std::move(temp._name); temp._buffer
= nullptr; temp._size
= 0; return
*this; }};template
<typename
T>Buffer<T>
getBuffer(const
std::string& name) { Buffer<T>
b(name, 128); return
b;}int
main(){ Buffer<int>
b1; Buffer<int>
b2("buf2",
64); Buffer<int>
b3 = b2; Buffer<int>
b4 = getBuffer<int>("buf4"); b1
= getBuffer<int>("buf5"); return
0;} |
默认的copy constructor以及copy assignment operator大家应该很熟悉了。C++11中新增的是move constructor以及move assignment operator,这两个函数根据上文所描述的move语义实现。如果你运行这段代码,你就会发现b4构造时,move constructor会被调用。同样,对b1赋值时,move assignment operator会被调用。原因就在于getBuffer()的返回值是一个临时对象——也就是右值。
你也许注意到了,move constuctor中当我们初始化变量name和指向buffer的指针时,我们使用了std::move。name实际上是一个string,std::string实现了move语义。std::unique_ptr也一样。但是如果我们写_name(temp._name),那么copy constructor将会被调用。不过对于_buffer来说不能这么写,因为std::unique_ptr没有copy constructor。但为什么std::string的move constructor此时没有被调到呢?这是因为虽然我们使用一个右值调用了Buffer的move constructor,但在这个构造函数内,它实际上是个左值。为什么?因为它是有名字的——“temp”。一个有名字的对象就是左值。为了再把它变为右值(以便调用move constructor)必须使用std::move。这个函数仅仅是把一个左值引用变为一个右值引用。
更新:虽然这个例子是为了说明如何实现move constructor以及move assignment operator,但具体的实现方式并不是唯一的。在本文的回复中Member 7805758同学提供了另一种可能的实现。为了方便查看,我把它也列在下面:
|
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
43
44
45
46
47
|
template
<typename
T>class
Buffer{ std::string
_name; size_t
_size; std::unique_ptr<T[]>
_buffer;public: //
constructor Buffer(const
std::string& name = "",
size_t
size = 16): _name(name), _size(size), _buffer(size?
new
T[size] : nullptr) {} //
copy constructor Buffer(const
Buffer& copy): _name(copy._name), _size(copy._size), _buffer(copy._size?
new
T[copy._size] : nullptr) { T*
source = copy._buffer.get(); T*
dest = _buffer.get(); std::copy(source,
source + copy._size, dest); } //
copy assignment operator Buffer&
operator=(Buffer copy) { swap(*this,
copy); return
*this; } //
move constructor Buffer(Buffer&&
temp):Buffer() { swap(*this,
temp); } friend
void
swap(Buffer& first, Buffer& second) noexcept { using
std::swap; swap(first._name
, second._name); swap(first._size
, second._size); swap(first._buffer,
second._buffer); } |
848

被折叠的 条评论
为什么被折叠?



