move对效率的影响

下面自己实现了字符串类,命名为String.然后分为类实现移动构造函数(mctor)和移动赋值运算符函数(masgn).并且对比在该类有mctor和masgn与没有这两个函数时的效率差异.

实现代码

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//
// Created by xixi2 on 19-9-15.
//

#include <cstring>
#include <string>
#include <iostream>
#include <vector>

using namespace std;


/**
* string的实现可以有两种方法:
* 第一种是以特定字符为结束符
* 第二种是包含长度
*/


/**
* class with pointer members
*/
class String {
// friend std::ostream &operator<<(std::ostream &out, const String &rhs);

public:
static size_t dctor; // 累计默认构造函数调用次数
static size_t ctor; // 累计ctor调用次数
static size_t cctor; // 累计copy-ctor调用次数
static size_t casgn; // 累计copy-asgn调用次数
static size_t mctor; // 累计move-cotr调用次数
static size_t masgn; // 累计move-asgn调用次数
static size_t dtor; // 累计析构函数调用次数

String() : _data(nullptr), _len(0) {
++dctor;
}

explicit String(const char *p);

String(const String &str);

String(String &&str) noexcept;

String &operator=(const String &str);

String &operator=(String &&str) noexcept; // 移动构造函数

~String();

char *get_c_str() const {
return _data;
}

// 为了实现异常安全
// member swap:不允许抛异常
void swap(String &other);


// 为了能够放到特殊容器,如map中
bool operator<(const String &rhs) const;

bool operator==(const String &rhs) const;

private:
char *_data;
size_t _len;

void _init_data(const char *s) {
_data = new char[_len + 1];
if (s) {
memcpy(_data, s, _len);
}
_data[_len] = '\0';
}
};


// 静态数据成员需要在类外初始化
size_t String::dctor = 0; // 累计默认构造函数调用次数
size_t String::ctor = 0; // 累计ctor调用次数
size_t String::cctor = 0; // 累计copy-ctor调用次数
size_t String::casgn = 0; // 累计copy-asgn调用次数
size_t String::mctor = 0; // 累计move-cotr调用次数
size_t String::masgn = 0; // 累计move-asgn调用次数
size_t String::dtor = 0;


String::String(const char *p) : _len(strlen(p)) { // 这里p没有默认值nullptr,不需要判断p是否为空
++ctor;
_init_data(_data);
}


// 这里为什么不直接在初始化列表中调用_init_data()函数:是因为形式不对吗
String::String(const String &str) : _len(str._len) {
++cctor;
_init_data(str._data);
}


// copy assignment
String &String::operator=(const String &str) {
++casgn;
if (this == &str) { // 检测自我赋值
if (_data) {
delete _data;
}
_len = str._len;
_init_data(str._data);
}
return *this;
}


// 移动构造函数
String::String(String &&str) noexcept : _len(str._len), _data(str._data) {
++mctor;

// 让移动源对象处于可析构且有效的状态
str._len = 0;
str._data = nullptr;
}


String &String::operator=(String &&str) noexcept {
++masgn;

// 如果对右值引用取地址,那么这里是否需要考虑自我赋值问题?
// str是个变量,是左值,但是它的类型是右值引用类型,即它表示的是一个临时对象
if (this == &str) {
if (_data) {
delete _data;
}
_len = str._len;
_data = str._data;
str._len = 0;
str._data = nullptr;
}
return *this;
}

String::~String() {
++ctor;
if (_data) {
delete _data;
}
}

bool String::operator<(const String &rhs) const {
return string(this->_data) < string(rhs._data);
}

bool String::operator==(const String &rhs) const {
return string(_data) == string(rhs._data);
}

std::ostream &operator<<(std::ostream &out, const String &rhs) {
// 为了测试,当字符串为空时,输出empty String
if (*rhs.get_c_str() == '\0') {
out << "empty String";
}
out << rhs.get_c_str();
return out;
}


namespace std {
// 特例化模板
template<>
struct hash<String> {
size_t operator()(const String &s) const noexcept {
return hash<string>()(string(s.get_c_str()));
}
};
}

template<typename T>
void output_static_data(const T &myStr) {
cout << "typeid(myStr).name(): " << typeid(myStr).name() << "--" << endl;
cout << "cctor=" << T::cctor
<< " mctor=" << T::mctor
<< " casgn=" << T::casgn
<< " masgn=" << T::masgn
<< " dtor=" << T::dtor
<< " ctor=" << T::ctor
<< " dctor=" << T::dctor << endl;
}

template<typename M>
void test_moveable(M c1, long &value) {
char buf[10];

typedef typename iterator_traits<typename M::iterator>::value_type v1type;
clock_t start = clock();
for (long i = 0; i < value; ++i) {
snprintf(buf, 10, "%d", rand() + 1); // 随机数,放进buf
auto ite = c1.end(); // 定位尾端

// vltype(buf)是临时对象,调用的是移动构造函数
c1.insert(ite, v1type(buf));
}

cout << "construction, milli-seconds: " << (clock() - start) << endl;
cout << "size()=" << c1.size() << endl;

output_static_data(*(c1.begin())); // 调用拷贝构造函数+

cout << endl;

start = clock();
M c11(c1);
cout << "container copy, milli-seconds: " << (clock() - start) << endl;

start = clock();
M c12(std::move(c1));
cout << "container move, milli-seconds: " << (clock() - start) << endl;

start = clock();
c11.swap(c12);
cout << "container swap, milli-seconds: " << (clock() - start) << endl;
}

运行结果与效率比较

vector

1
2
3
4
5
6
7
8
9
10
void test_vector() {
long value = 3000000;
// value = 10;
test_moveable(vector<String>(), value);
}

int main() {
test1();
return 0;
}

对于vector,有mctor和masgn的情况下的运行结果(时间单位毫秒)

1
2
3
4
5
6
7
8
construction, milli-seconds: 709342
size()=3000000
typeid(myStr).name(): 6String--
cctor=0 mctor=7194303 casgn=0 masgn=0 dtor=7194303 ctor=3000000 dctor=0

container copy, milli-seconds: 171571
container move, milli-seconds: 1
container swap, milli-seconds: 0

调用了7194303次移动构造函数.之所以这里的调用移动构造函数次数多于元素个数是因为vector在插入过程,一旦内存不足,需要扩容,而扩容的过程是分配一块新的内存,然后将旧的元素搬到新的内存中,再释放新的内存.

没有这两个函数时的运行结果

1
2
3
4
5
6
7
8
construction, milli-seconds: 912494
size()=3000000
typeid(myStr).name(): 6String--
cctor=7194303 mctor=0 casgn=0 masgn=0 dtor=7194303 ctor=3000000 dctor=0

container copy, milli-seconds: 169270
container move, milli-seconds: 1
container swap, milli-seconds: 1

调用的是拷贝构造函数.

list

1
2
3
4
5
void test_list() {
long value = 3000000;
// value = 10;
test_moveable(list<String>(), value);
}

运行结果

有mctor和masgn的情况下

1
2
3
4
5
6
7
8
construction, milli-seconds: 771100
size()=3000000
typeid(myStr).name(): 6String--
cctor=0 mctor=3000000 casgn=0 masgn=0 dtor=3000000 ctor=3000000 dctor=0

container copy, milli-seconds: 453080
container move, milli-seconds: 16
container swap, milli-seconds: 1

没有mctor和masgn的情况下

1
2
3
4
5
6
7
8
construction, milli-seconds: 837069
size()=3000000
typeid(myStr).name(): 6String--
cctor=3000000 mctor=0 casgn=0 masgn=0 dtor=3000000 ctor=3000000 dctor=0

container copy, milli-seconds: 433835
container move, milli-seconds: 1
container swap, milli-seconds: 1

对于list,有无移动构造函数,没有特别大的影响.

deque

1
2
3
4
5
void test_deque() {
long value = 3000000;
// value = 10;
test_moveable(deque<String>(), value);
}

运行结果

有mctor和masgn的情况下

1
2
3
4
5
6
7
8
construction, milli-seconds: 621955
size()=3000000
typeid(myStr).name(): 6String--
cctor=0 mctor=3000000 casgn=0 masgn=0 dtor=3000000 ctor=3000000 dctor=0

container copy, milli-seconds: 201950
container move, milli-seconds: 17
container swap, milli-seconds: 1

没有mctor和masgn的情况下

1
2
3
4
5
6
7
8
construction, milli-seconds: 697399
size()=3000000
typeid(myStr).name(): 6String--
cctor=3000000 mctor=0 casgn=0 masgn=0 dtor=3000000 ctor=3000000 dctor=0

container copy, milli-seconds: 200969
container move, milli-seconds: 18
container swap, milli-seconds: 2

对于deque,在尾端插入的情况,影响同样不大,但是如果在中间位置输入,则会影响较大.