C++ 巩固进阶之一

大纲

C++ 11 的右值引用

重现模拟实现字符串类的问题

下述代码模拟实现了 C++ 中的 string 类,但是 main() 函数调用 getString() 函数的效率会非常低。第一个原因是,getString() 函数在调用结束时,返回了一个 MyString 对象,这会调用一次拷贝构造函数来拷贝 tmpStr 对象。第二个原因是,在 main() 函数中,将 getStriing() 函数的返回值赋值了给 str2 对象,这会调用赋值运算符重载函数,也就是又拷贝了一次数据。点击 查看完整分析图解。

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
#include <iostream>
#include <cstring>

using namespace std;

class MyString {

public:
// 构造函数
MyString(const char *p = nullptr) {
cout << "MyString(const char *p = nullptr)" << endl;
if (p != nullptr) {
_pstr = new char[strlen(p) + 1];
strcpy(_pstr, p);
} else {
_pstr = new char[1];
*_pstr = '\0';
}
}

// 析构函数
~MyString() {
cout << "~MyString()" << endl;
delete[] _pstr;
_pstr = nullptr;
}

// 拷贝构造函数
MyString(const MyString &str) {
cout << "MyString(const MyString &str)" << endl;
// 深拷贝
_pstr = new char[strlen(str._pstr) + 1];
strcpy(_pstr, str._pstr);
}

// 赋值运算符重载
MyString &operator=(const MyString &str) {
cout << "operator=(const MyString &str)" << endl;
// 防止自赋值
if (this == &str) {
return *this;
}

// 释放原来的内存空间
delete[] _pstr;

// 深拷贝
_pstr = new char[strlen(str._pstr) + 1];
strcpy(_pstr, str._pstr);

return *this;
}

// 加法运算符重载
friend MyString operator+(const MyString &str1, const MyString &str2);

// 左移运算符重载
friend ostream &operator<<(ostream &out, const MyString &str);

// 返回字符串自身
const char *c_str() const {
return _pstr;
}

private:
char *_pstr;
};

MyString operator+(const MyString &str1, const MyString &str2) {
MyString tmpStr;
tmpStr._pstr = new char[strlen(str1._pstr) + strlen(str2._pstr) + 1];
strcpy(tmpStr._pstr, str1._pstr);
strcat(tmpStr._pstr, str2._pstr);
return tmpStr;
}

ostream &operator<<(ostream &out, const MyString &str) {
out << str._pstr;
return out;
}

MyString getString(MyString &str) {
const char *pstr = str.c_str();
MyString tmpStr(pstr);
return tmpStr;
}

int main() {
MyString str1("aaaaaaa");
MyString str2;
str2 = getString(str1);
cout << str2.c_str() << endl;
return 0;
}

在 Visual Studio 的 Debug 模式下,程序运行的输出结果如下:

1
2
3
4
5
6
7
8
9
10
MyString(const char *p = nullptr)       // main() 函数中构造 str1 对象
MyString(const char *p = nullptr) // main() 函数中构造 str2 对象
MyString(const char *p = nullptr) // getString() 函数中构造 tmpStr 对象
MyString(const MyString &str) // getString() 函数返回执行结果值时,调用拷贝构造函数来拷贝 tmpStr 对象给 main() 函数栈帧上的临时对象
~MyString() // 析构 getString() 函数中的 tmpStr 对象
operator=(const MyString &str) // main() 函数中,执行赋值运算符重载函数来将 main() 函数栈帧上的临时对象赋值给 str2 对象
~MyString() // 析构 main() 函数栈帧上的临时对象
aaaaaaa
~MyString() // 析构 main() 函数中的 str2 对象
~MyString() // 析构 main() 函数中的 str1 对象

在 Visual Studio 的 Release 模式下,由于编译器的优化,程序运行的输出结果如下:

1
2
3
4
5
6
7
8
MyString(const char *p = nullptr)
MyString(const char *p = nullptr)
MyString(const char *p = nullptr)
operator=(const MyString &str)
~MyString()
aaaaaaa
~MyString()
~MyString()

解决模拟实现字符串类的问题

为了解决上述模拟实现字符串类时,多次拷贝内存数据导致运行效率低的问题,可以使用带右值引用参数的拷贝构造函数和赋值运算符重载函数来解决。

左值引用和右值引用

概念介绍
  • 左值引用与右值引用的介绍

    • 左值:有名称、有内存
    • 右值:没名称(临时量)、没内存
  • 左值引用与右值引用的区别

    • 左值引用和右值引用的主要区别在于它们可以绑定的值类别,左值引用只能绑定到左值,而右值引用只能绑定到右值。
    • 右值引用引入了 move 移动语义,使得 C++ 可以更高效地处理临时对象。
    • 在泛型编程中,可以通过函数模板的类型推导来同时处理左值引用和右值引用,从而实现参数的 forward 完美转发。
案例代码
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
#include <iostream>

using namespace std;

class MyString {

public:
// 构造函数
MyString(const char* p = nullptr) {
cout << "MyString(const char *p = nullptr)" << endl;
if (p != nullptr) {
_pstr = new char[strlen(p) + 1];
strcpy(_pstr, p);
}
else {
_pstr = new char[1];
*_pstr = '\0';
}
}

......(省略)

private:
char* _pstr;

};

int main() {
int a = 10; // 左值:有名称、有内存,右值:没名称(临时量)、没内存

int& b = a; // 可以将左值绑定到左值引用上
// int&& c = a; // 无法将左值绑定到右值引用上

int&& d = 20; // 可以将右值绑定到右值引用上
// int& c = 20; // 无法将右值绑定到左值引用上

// int&& f = d; // 无法将左值绑定到右值引用上,因为右值引用变量本身就是左值

// MyString& s = MyString("aaa"); // 错误写法
MyString&& s = MyString("aaa"); // 正确写法

return 0;
}

解决实现字符串类的问题

提示

  • (1) 右值引用参数常用于减少内存数据的拷贝,比如在带右值引用参数的拷贝构造函数和赋值运算符重载函数中使用。
  • (2) 在带右值引用参数的拷贝构造函数和赋值运算符重载函数中,该右值引用参数接收的都是临时对象。
案例代码

这里使用带右值引用参数的拷贝构造函数和赋值运算符重载函数,来解决在模拟实现 C++ 字符串类时,多次拷贝内存数据导致运行效率低的问题。

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
#include <iostream>
#include <cstring>

using namespace std;

class MyString {

public:
// 构造函数
MyString(const char* p = nullptr) {
cout << "MyString(const char *p = nullptr)" << endl;
if (p != nullptr) {
_pstr = new char[strlen(p) + 1];
strcpy(_pstr, p);
}
else {
_pstr = new char[1];
*_pstr = '\0';
}
}

// 析构函数
~MyString() {
cout << "~MyString()" << endl;
delete[] _pstr;
_pstr = nullptr;
}

// 带左值引用参数的拷贝构造函数
MyString(const MyString& str) {
cout << "MyString(const MyString &str)" << endl;
// 深拷贝
_pstr = new char[strlen(str._pstr) + 1];
strcpy(_pstr, str._pstr);
}

// 带右值引用参数的拷贝构造函数
MyString(MyString&& str) {
cout << "MyString(MyString&& str)" << endl;
// 浅拷贝临时对象(可以提高代码执行效率,减少内存数据的拷贝次数)
_pstr = str._pstr;
str._pstr = nullptr;
}

// 带左值引用参数的赋值运算符重载
MyString& operator=(const MyString& str) {
cout << "operator=(const MyString &str)" << endl;
// 防止自赋值
if (this == &str) {
return *this;
}

// 释放原来的内存空间
delete[] _pstr;

// 深拷贝
_pstr = new char[strlen(str._pstr) + 1];
strcpy(_pstr, str._pstr);

return *this;
}

// 带右值引用参数的赋值运算符重载
MyString& operator=(MyString&& str) {
cout << "operator=(MyString&& str)" << endl;
// 防止自赋值
if (this == &str) {
return *this;
}

// 释放原来的内存空间
delete[] _pstr;

// 浅拷贝临时对象(可以提高代码执行效率,减少内存数据的拷贝次数)
_pstr = str._pstr;
str._pstr = nullptr;

return *this;
}


// 加法运算符重载
friend MyString operator+(const MyString& str1, const MyString& str2);

// 左移运算符重载
friend ostream& operator<<(ostream& out, const MyString& str);

// 返回字符串自身
const char* c_str() const {
return _pstr;
}

private:
char* _pstr;
};

MyString operator+(const MyString& str1, const MyString& str2) {
MyString tmpStr;
tmpStr._pstr = new char[strlen(str1._pstr) + strlen(str2._pstr) + 1];
strcpy(tmpStr._pstr, str1._pstr);
strcat(tmpStr._pstr, str2._pstr);
return tmpStr;
}

ostream& operator<<(ostream& out, const MyString& str) {
out << str._pstr;
return out;
}

MyString getString(MyString& str) {
const char* pstr = str.c_str();
MyString tmpStr(pstr);
return tmpStr;
}
测试代码一
1
2
3
4
5
6
7
int main() {
MyString str1("aaaaaaa");
MyString str2;
str2 = getString(str1);
cout << str2.c_str() << endl;
return 0;
}

在 Visual Studio 的 Debug 模式下,程序运行的输出结果如下:

1
2
3
4
5
6
7
8
9
10
MyString(const char *p = nullptr)       // main() 函数中构造 str1 对象
MyString(const char *p = nullptr) // main() 函数中构造 str2 对象
MyString(const char *p = nullptr) // getString() 函数中构造 tmpStr 对象
MyString(MyString&& str) // getString() 函数返回执行结果值时,调用带右值引用参数的拷贝构造函数来拷贝 tmpStr 对象给 main() 函数栈帧上的临时对象(使用浅拷贝提高运行效率)
~MyString() // 析构 getString() 函数中的 tmpStr 对象
operator=(MyString&& str) // main() 函数中,执行带右值引用参数的赋值运算符重载函数来将 main() 函数栈帧上的临时对象赋值给 str2 对象(使用浅拷贝提高运行效率)
~MyString() // 析构 main() 函数栈帧上的临时对象
aaaaaaa
~MyString() // 析构 main() 函数中的 str2 对象
~MyString() // 析构 main() 函数中的 str1 对象

在 Visual Studio 的 Release 模式下,由于编译器的优化,程序运行的输出结果如下:

1
2
3
4
5
6
7
8
9
================ test01() ================
MyString(const char *p = nullptr)
MyString(const char *p = nullptr)
MyString(const char *p = nullptr)
operator=(MyString&& str)
~MyString()
aaaaaaa
~MyString()
~MyString()
测试代码二
1
2
3
4
5
6
7
8
9
int main() {
MyString str1("Hello ");
MyString str2("World!");
cout << "---------------------------------" << endl;
MyString str3 = str1 + str2;
cout << "---------------------------------" << endl;
cout << str3 << endl;
return 0;
}

在 Visual Studio 的 Debug 模式下,程序运行的输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
MyString(const char *p = nullptr)
MyString(const char *p = nullptr)
---------------------------------
MyString(const char *p = nullptr)
MyString(MyString&& str)
~MyString()
---------------------------------
Hello World!
~MyString()
~MyString()
~MyString()

在 Visual Studio 的 Release 模式下,由于编译器的优化,程序运行的输出结果如下:

1
2
3
4
5
6
7
8
9
MyString(const char *p = nullptr)
MyString(const char *p = nullptr)
---------------------------------
MyString(const char *p = nullptr)
---------------------------------
Hello World!
~MyString()
~MyString()
~MyString()
测试代码三
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
void test01() {
cout << "\n============ test01() ============" << endl;

MyString str1 = "aaa";
vector<MyString> v1;

cout << "----------------------------------" << endl;
v1.push_back(str1); // 调用的是带左值引用参数的拷贝构造函数
cout << "----------------------------------" << endl;
}

void test02() {
cout << "\n============ test02() ============" << endl;

vector<MyString> v1;
cout << "----------------------------------" << endl;
v1.push_back(MyString("bbb")); // 调用的是带右值引用参数的拷贝构造函数
cout << "----------------------------------" << endl;
}

int main() {
test01();
test02();
return 0;
}

在 Visual Studio 中,无论是 Debug 模式,还是 Release 模式,程序运行的输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
============ test01() ============
MyString(const char *p = nullptr)
----------------------------------
MyString(const MyString &str)
----------------------------------
~MyString()
~MyString()

============ test02() ============
----------------------------------
MyString(const char *p = nullptr)
MyString(MyString&& str)
~MyString()
----------------------------------
~MyString()

C++ 11 的移动语义与完美转发

基础案例代码

自定义字符串类

自定义一个 MyString 字符串类,模拟实现 C++ 的 string 字符串类,主要使用带右值引用参数的拷贝构造函数和赋值运算符重载函数来减少内存数据的拷贝次数。

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
#include <iostream>
#include <cstring>

using namespace std;

class MyString {

public:
// 构造函数
MyString(const char* p = nullptr) {
cout << "MyString(const char *p = nullptr)" << endl;
if (p != nullptr) {
_pstr = new char[strlen(p) + 1];
strcpy(_pstr, p);
}
else {
_pstr = new char[1];
*_pstr = '\0';
}
}

// 析构函数
~MyString() {
cout << "~MyString()" << endl;
delete[] _pstr;
_pstr = nullptr;
}

// 带左值引用参数的拷贝构造函数
MyString(const MyString& str) {
cout << "MyString(const MyString &str)" << endl;
// 深拷贝
_pstr = new char[strlen(str._pstr) + 1];
strcpy(_pstr, str._pstr);
}

// 带右值引用参数的拷贝构造函数
MyString(MyString&& str) {
cout << "MyString(MyString&& str)" << endl;
// 浅拷贝临时对象(可以提高代码执行效率,减少内存数据的拷贝次数)
_pstr = str._pstr;
str._pstr = nullptr;
}

// 带左值引用参数的赋值运算符重载
MyString& operator=(const MyString& str) {
cout << "operator=(const MyString &str)" << endl;
// 防止自赋值
if (this == &str) {
return *this;
}

// 释放原来的内存空间
delete[] _pstr;

// 深拷贝
_pstr = new char[strlen(str._pstr) + 1];
strcpy(_pstr, str._pstr);

return *this;
}

// 带右值引用参数的赋值运算符重载
MyString& operator=(MyString&& str) {
cout << "operator=(MyString&& str)" << endl;
// 防止自赋值
if (this == &str) {
return *this;
}

// 释放原来的内存空间
delete[] _pstr;

// 浅拷贝临时对象(可以提高代码执行效率,减少内存数据的拷贝次数)
_pstr = str._pstr;
str._pstr = nullptr;

return *this;
}

// 加法运算符重载
friend MyString operator+(const MyString& str1, const MyString& str2);

// 左移运算符重载
friend ostream& operator<<(ostream& out, const MyString& str);

// 返回字符串自身
const char* c_str() const {
return _pstr;
}

private:
char* _pstr;
};

MyString operator+(const MyString& str1, const MyString& str2) {
MyString tmpStr;
tmpStr._pstr = new char[strlen(str1._pstr) + strlen(str2._pstr) + 1];
strcpy(tmpStr._pstr, str1._pstr);
strcat(tmpStr._pstr, str2._pstr);
return tmpStr;
}

ostream& operator<<(ostream& out, const MyString& str) {
out << str._pstr;
return out;
}

自定义 Vector 容器类

自定义一个 Vector 容器类,模拟实现 C++ 的 vector 容器,主要实现了 vector 容器的空间分配器、迭代器。值得一提的是,由于篇幅有限,这里虽然解决迭代器失效的问题,但是并没有解决容器扩容(执行插入操作)后导致迭代器失效的问题。

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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
#include <iostream>
#include <cstring>

using namespace std;

// 类模板
template<typename T>
// 空间配置器(负责内存开辟、内存释放、对象构造、对象析构)
struct Allocator {

// 数组的内存开辟
T *allocate(size_t size) {
return (T *) malloc(sizeof(T) * size);
}

// 数组的内存释放
void deallocate(void *p) {
free(p);
}

// 对象构造
void construct(T *p, const T &val) {
// 在指定的内存上构造对象(定位 new)
new(p)T(val);
}

// 对象析构
void destroy(T *p) {
// ~T() 代表了 T 类型对象的析构函数
p->~T();
}

};

// 类模板
template<typename T, typename Alloc = Allocator<T>>
// 向量容器
class Vector {

public:
// 构造函数
Vector(int size = 10) {
// 开辟数组的内存空间
_first = _allocator.allocate(size);
_last = _first;
_end = _first + size;
}

// 析构函数(先析构容器内的有效元素,然后再释放 _first 指针指向的堆内存)
~Vector() {
// 析构容器内的有效元素
for (T *p = _first; p != _last; p++) {
_allocator.destroy(p);
}

// 释放堆上的数组内存
_allocator.deallocate(_first);
_first = _last = _end = nullptr;
}

// 拷贝构造函数
Vector(const Vector<T> &v) {
// 容器的总大小
int size = v._end - v._first;
// 有效元素的个数
int length = v._last - v._first;

// 开辟数组的内存空间
_first = _allocator.allocate(size);

// 在指定的内存空间中构造对象
for (int i = 0; i < length; i++) {
_allocator.construct(_first + i, v._first[i]);
}
_last = _first + length;
_end = _first + size;
}

// 赋值运算符重载
Vector<T> &operator=(const Vector<T> &v) {
if (this == v) {
return *this;
}

// 析构容器内的有效元素
for (T *p = _first; p != _last; p++) {
_allocator.destroy(p);
}

// 释放堆上的数组内存
_allocator.deallocate(_first);

// 容器的总大小
int size = v._end - v._first;
// 有效元素的个数
int length = v._last - v._first;

// 开辟数组的内存空间
_first = _allocator.allocate(size);

// 在指定的内存空间中构造对象
for (int i = 0; i < length; i++) {
_allocator.construct(_first + i, v._first[i]);
}
_last = _first + length;
_end = _first + size;

return *this;
}

// 往容器尾部添加元素
void push_back(const T &val) {
if (full()) {
resize();
}
// 在指定的内存空间中构造对象
_allocator.construct(_last, val);
_last++;
}

// 从容器尾部删除元素(需要将对象的析构和内存释放分开处理)
void pop_back() {
if (!empty()) {
verify(_last - 1, _last - 1);
_last--;
// 在指定的内存空间中析构对象
_allocator.destroy(_last);
}
}

// 返回容器尾部的元素
T back() const {
if (empty()) {
throw "Vector is empty!";
}
return *(_last - 1);
}

// 容器是否满了
bool full() const {
return _last == _end;
}

// 容器是否为空
bool empty() const {
return _first == _last;
}

// 返回有效元素的个数
int size() const {
return _last - _first;
}

// 重载中括号运算符
T &operator[](int index) {
if (index < 0 || index >= size()) {
throw "OutOfRangeException";
}
return _first[index];
}

// 迭代器
class iterator {

public:

friend class Vector<T, Alloc>;

iterator(Vector<T, Alloc> *pvec = nullptr, T *p = nullptr) : _pVec(pvec), _ptr(p) {
// 维护迭代器的单向链表结构
Iterator_Base *itb = new Iterator_Base(this, _pVec->_head._next);
_pVec->_head._next = itb;
}

// 重载不等于运算符
bool operator!=(const iterator &other) const {
// 判断迭代器指向的容器是不是同一个
if (_pVec == nullptr || _pVec != other._pVec) {
throw "Iterator incompatable!";
}
return _ptr != other._ptr;
}

// 重载前置 ++ 运算符
iterator &operator++() {
// 检测迭代器的有效性
if (_pVec == nullptr) {
throw "Iterator invalid!";
}
_ptr++;
return *this;
}

// 重载后置 ++ 运算符
iterator operator++(int) {
// 检测迭代器的有效性
if (_pVec == nullptr) {
throw "Iterator invalid!";
}
return iterator(_ptr++);
}

// 解引用运算符重载
T &operator*() const {
// 检测迭代器的有效性
if (_pVec == nullptr) {
throw "Iterator invalid!";
}
return *_ptr;
}

private:
T *_ptr;
Vector<T, Alloc> *_pVec; // 当前迭代器是哪个容器的对象
};

// 返回的是容器底层首元素的迭代器的表示
iterator begin() {
return iterator(this, _first);
}

// 返回的是容器末尾元素后继位置的迭代器的表示
iterator end() {
return iterator(this, _last);
}

// 通过迭代器往容器插入元素
// 这里暂时不考虑容器扩容,也不考虑 it._prt 的指针合法性
iterator insert(iterator it, const T &val) {
verify(it._ptr - 1, _last);

// 重新分配数组的内存空间,并往右边移动数组元素
T *p = _last;
while (p > it._ptr) {
_allocator.construct(p, *(p - 1));
_allocator.destroy(p - 1);
p--;
}
_allocator.construct(p, val);

_last++;
return iterator(this, p);
}

// 通过迭代器往容器删除元素
iterator erase(iterator it) {
verify(it._ptr - 1, _last);

// 重新分配数组的内存空间,并往左边移动数组元素
T *p = it._ptr;
while (p < _last - 1) {
_allocator.destroy(p);
_allocator.construct(p, *(p + 1));
p++;
}
_allocator.destroy(p);

_last--;
return iterator(this, it._ptr);
}

private:
T *_first; // 指向数组起始的位置
T *_last; // 指向数组中有效元素的后继位置
T *_end; // 指向数组空间的后继位置
Alloc _allocator; // 定义容器空间配置器的对象

// 迭代器的单向链表结构
struct Iterator_Base {
Iterator_Base(iterator *cur = nullptr, Iterator_Base *next = nullptr) : _cur(cur), _next(next) {

}

iterator *_cur;
Iterator_Base *_next;
};

Iterator_Base _head;

// 扩容操作
void resize() {
int size = _end - _first;
// 开辟数组的内存空间
T *_ptemp = _allocator.allocate(size * 2);
// 在指定的内存空间中构造对象
for (int i = 0; i < size; i++) {
_allocator.construct(_ptemp + i, _first[i]);
}

// 析构原来容器内的有效元素
for (T *p = _first; p != _last; p++) {
_allocator.destroy(p);
}

// 释放原来的数组内存
_allocator.deallocate(_first);

_first = _ptemp;
_last = _first + size;
_end = _first + size * 2;
}

// 维护迭代器的单向链表
void verify(T *start, T *end) {
Iterator_Base *cur = &this->_head;
Iterator_Base *next = this->_head._next;
while (next != nullptr) {
if (next->_cur->_ptr >= start && next->_cur->_ptr <= end) {
// 迭代器失效,将迭代器持有的容器指针置为空
next->_cur->_pVec = nullptr;
// 在迭代器链表中,删除当前迭代器节点,并继续判断后面的迭代器节点是否失效
cur->_next = next->_next;
delete next;
next = cur->_next;
} else {
next = next->_next;
}
}
}

};

自定义 Vector 类存在的问题

使用上面自定义的 Vecotr 容器类执行以下测试代码后,发现执行 vector.push_back(MyString("bbb")) 时,会调用 MyString 类带左值引用参数的拷贝构造函数,这会导致多拷贝一份内存数据,从而影响程序的执行效率。值得一提的是,这里希望调用的是 MyString 类带右值引用参数的拷贝构造函数,因为该带右值引用参数的拷贝构造函数使用的是浅拷贝,可以减少内存数据的拷贝次数。

1
2
3
4
5
6
7
int main() {
Vector<MyString> v1;
cout << "----------------------------------" << endl;
v1.push_back(MyString("bbb")); // 调用的是 MyString 带左值引用参数的拷贝构造函数
cout << "----------------------------------" << endl;
return 0;
}

在 Visual Studio 的 Debug 模式下,程序运行的输出结果如下:

1
2
3
4
5
6
----------------------------------
MyString(const char *p = nullptr)
MyString(const MyString &str)
~MyString()
----------------------------------
~MyString()

move 移动语义

move 移动语义的概念

C++ 中的移动语义是引入于 C++ 11 的一种特性,它通过引入移动构造函数和移动赋值运算符,允许程序以更高效的方式管理资源,尤其是在避免不必要的拷贝操作时。移动语义的核心是利用右值引用(T&&)和标准库中的 std::move,使对象的资源从一个对象转移到另一个对象(比如将左值类型强转为右值类型),而不是拷贝,从而提高程序性能。

  • 移动语义的主要作用

    • 高效资源转移:避免资源分配和释放的重复工作。
    • 减少拷贝:通过转移所有权来减少对象的拷贝。
    • 提高程序性能:尤其适用于内存密集型和资源管理复杂的程序。
  • 移动语义的使用场景

    • 减少临时对象的开销(如函数返回大对象)。
    • 避免深拷贝(如容器中的数据转移)。

move 移动语义的使用

在上面自定义的 Vector 容器类中,为了解决执行 vector.push_back(MyString("bbb")) 时,会调用 MyString 类带左值引用参数的拷贝构造函数,从而导致多拷贝一份内存数据的问题,可以使用 C++ 中的 move 移动语义来解决。

  • 更改的核心代码
    • 重载 Allocator::construct() 函数,分别用于接收左值引用参数和右值引用参数,并使用 move 移动语义将左值类型强转为右值类型
    • 重载 Vector::push_back() 函数,分别用于接收左值引用参数和右值引用参数,并使用 move 移动语义将左值类型强转为右值类型
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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
#include <iostream>
#include <cstring>
#include "MyString.h"

using namespace std;

// 类模板
template<typename T>
// 空间配置器(负责内存开辟、内存释放、对象构造、对象析构)
struct Allocator {

// 数组的内存开辟
T* allocate(size_t size) {
return (T*)malloc(sizeof(T) * size);
}

// 数组的内存释放
void deallocate(void* p) {
free(p);
}

// 对象构造(接收左值引用参数)
void construct(T* p, const T& val) {
// 在指定的内存上构造对象(定位 new)
new(p)T(val);
}

// 对象构造(接收右值引用参数)
void construct(T* p, T&& val) {
// 在指定的内存上构造对象(定位 new)
// move 是移动语义,可以将左值类型强转为右值类型
new(p)T(move(val));
}

// 对象析构
void destroy(T* p) {
// ~T() 代表了 T 类型对象的析构函数
p->~T();
}

};

// 类模板
template<typename T, typename Alloc = Allocator<T>>
// 向量容器
class Vector {

public:
// 构造函数
Vector(int size = 10) {
// 开辟数组的内存空间
_first = _allocator.allocate(size);
_last = _first;
_end = _first + size;
}

// 析构函数(先析构容器内的有效元素,然后再释放 _first 指针指向的堆内存)
~Vector() {
// 析构容器内的有效元素
for (T* p = _first; p != _last; p++) {
_allocator.destroy(p);
}

// 释放堆上的数组内存
_allocator.deallocate(_first);
_first = _last = _end = nullptr;
}

// 拷贝构造函数
Vector(const Vector<T>& v) {
// 容器的总大小
int size = v._end - v._first;
// 有效元素的个数
int length = v._last - v._first;

// 开辟数组的内存空间
_first = _allocator.allocate(size);

// 在指定的内存空间中构造对象
for (int i = 0; i < length; i++) {
_allocator.construct(_first + i, v._first[i]);
}
_last = _first + length;
_end = _first + size;
}

// 赋值运算符重载
Vector<T>& operator=(const Vector<T>& v) {
if (this == v) {
return *this;
}

// 析构容器内的有效元素
for (T* p = _first; p != _last; p++) {
_allocator.destroy(p);
}

// 释放堆上的数组内存
_allocator.deallocate(_first);

// 容器的总大小
int size = v._end - v._first;
// 有效元素的个数
int length = v._last - v._first;

// 开辟数组的内存空间
_first = _allocator.allocate(size);

// 在指定的内存空间中构造对象
for (int i = 0; i < length; i++) {
_allocator.construct(_first + i, v._first[i]);
}
_last = _first + length;
_end = _first + size;

return *this;
}

// 往容器尾部添加元素(接收左值引用参数)
void push_back(const T& val) {
if (full()) {
resize();
}
// 在指定的内存空间中构造对象
_allocator.construct(_last, val);
_last++;
}

// 往容器尾部添加元素(接收右值引用参数)
void push_back(T&& val) {
if (full()) {
resize();
}
// 在指定的内存空间中构造对象
// move 是移动语义,可以将左值类型强转为右值类型
_allocator.construct(_last, move(val));
_last++;
}

// 从容器尾部删除元素(需要将对象的析构和内存释放分开处理)
void pop_back() {
if (!empty()) {
verify(_last - 1, _last - 1);
_last--;
// 在指定的内存空间中析构对象
_allocator.destroy(_last);
}
}

// 返回容器尾部的元素
T back() const {
if (empty()) {
throw "Vector is empty!";
}
return *(_last - 1);
}

// 容器是否满了
bool full() const {
return _last == _end;
}

// 容器是否为空
bool empty() const {
return _first == _last;
}

// 返回有效元素的个数
int size() const {
return _last - _first;
}

// 重载中括号运算符
T& operator[](int index) {
if (index < 0 || index >= size()) {
throw "OutOfRangeException";
}
return _first[index];
}

// 迭代器
class iterator {

public:

friend class Vector<T, Alloc>;

iterator(Vector<T, Alloc>* pvec = nullptr, T* p = nullptr) : _pVec(pvec), _ptr(p) {
// 维护迭代器的单向链表结构
Iterator_Base* itb = new Iterator_Base(this, _pVec->_head._next);
_pVec->_head._next = itb;
}

// 重载不等于运算符
bool operator!=(const iterator& other) const {
// 判断迭代器指向的容器是不是同一个
if (_pVec == nullptr || _pVec != other._pVec) {
throw "Iterator incompatable!";
}
return _ptr != other._ptr;
}

// 重载前置 ++ 运算符
iterator& operator++() {
// 检测迭代器的有效性
if (_pVec == nullptr) {
throw "Iterator invalid!";
}
_ptr++;
return *this;
}

// 重载后置 ++ 运算符
iterator operator++(int) {
// 检测迭代器的有效性
if (_pVec == nullptr) {
throw "Iterator invalid!";
}
return iterator(_ptr++);
}

// 解引用运算符重载
T& operator*() const {
// 检测迭代器的有效性
if (_pVec == nullptr) {
throw "Iterator invalid!";
}
return *_ptr;
}

private:
T* _ptr;
Vector<T, Alloc>* _pVec; // 当前迭代器是哪个容器的对象
};

// 返回的是容器底层首元素的迭代器的表示
iterator begin() {
return iterator(this, _first);
}

// 返回的是容器末尾元素后继位置的迭代器的表示
iterator end() {
return iterator(this, _last);
}

// 通过迭代器往容器插入元素
// 这里暂时不考虑容器扩容,也不考虑 it._prt 的指针合法性
iterator insert(iterator it, const T& val) {
verify(it._ptr - 1, _last);

// 重新分配数组的内存空间,并往右边移动数组元素
T* p = _last;
while (p > it._ptr) {
_allocator.construct(p, *(p - 1));
_allocator.destroy(p - 1);
p--;
}
_allocator.construct(p, val);

_last++;
return iterator(this, p);
}

// 通过迭代器往容器删除元素
iterator erase(iterator it) {
verify(it._ptr - 1, _last);

// 重新分配数组的内存空间,并往左边移动数组元素
T* p = it._ptr;
while (p < _last - 1) {
_allocator.destroy(p);
_allocator.construct(p, *(p + 1));
p++;
}
_allocator.destroy(p);

_last--;
return iterator(this, it._ptr);
}

private:
T* _first; // 指向数组起始的位置
T* _last; // 指向数组中有效元素的后继位置
T* _end; // 指向数组空间的后继位置
Alloc _allocator; // 定义容器空间配置器的对象

// 迭代器的单向链表结构
struct Iterator_Base {
Iterator_Base(iterator* cur = nullptr, Iterator_Base* next = nullptr) : _cur(cur), _next(next) {

}

iterator* _cur;
Iterator_Base* _next;
};

Iterator_Base _head;

// 扩容操作
void resize() {
int size = _end - _first;
// 开辟数组的内存空间
T* _ptemp = _allocator.allocate(size * 2);
// 在指定的内存空间中构造对象
for (int i = 0; i < size; i++) {
_allocator.construct(_ptemp + i, _first[i]);
}

// 析构原来容器内的有效元素
for (T* p = _first; p != _last; p++) {
_allocator.destroy(p);
}

// 释放原来的数组内存
_allocator.deallocate(_first);

_first = _ptemp;
_last = _first + size;
_end = _first + size * 2;
}

// 维护迭代器的单向链表
void verify(T* start, T* end) {
Iterator_Base* cur = &this->_head;
Iterator_Base* next = this->_head._next;
while (next != nullptr) {
if (next->_cur->_ptr >= start && next->_cur->_ptr <= end) {
// 迭代器失效,将迭代器持有的容器指针置为空
next->_cur->_pVec = nullptr;
// 在迭代器链表中,删除当前迭代器节点,并继续判断后面的迭代器节点是否失效
cur->_next = next->_next;
delete next;
next = cur->_next;
}
else {
next = next->_next;
}
}
}

};

int main() {
Vector<MyString> v1;
cout << "----------------------------------" << endl;
v1.push_back(MyString("bbb")); // 调用的是 MyString 带右值引用参数的拷贝构造函数
cout << "----------------------------------" << endl;
return 0;
}

在 Visual Studio 的 Debug 模式下,程序运行的输出结果如下:

1
2
3
4
5
6
----------------------------------
MyString(const char *p = nullptr)
MyString(MyString&& str)
~MyString()
----------------------------------
~MyString()

forward 完美转发

虽然使用 C++ 11 提供的 move 移动语义,可以解决在执行 vector.push_back(MyString("bbb")) 时,会调用 MyString 类带左值引用参数的拷贝构造函数,从而导致多拷贝一份内存数据的问题。但是,这样每次都需要定义两个重载函数来分别接收左值引用参数和右值引用参数,这就显得比较繁琐。为了使 C++ 代码更简洁,可以使用 C++ 11 提供的 forward 完美转发来实现同样的功能。

forward 完美转发的概念

完美转发(Perfect Forwarding)是 C++ 11 中引入的一种编程技巧,其目的是在编写泛型函数时能够保留参数的类型和值类别(左值或右值),从而实现更为高效且准确地传递参数。通过使用右值引用和模板类型推导,完美转发允许在函数中以原始参数的形式将参数传递给其他函数,而不会发生不必要的拷贝操作,从而提高性能。完美转发在很多场合都非常有用,尤其是在设计泛型库和需要高效参数传递的场景。以下是一些常见的完美转发应用场景:

  • (1) 委托构造函数:完美转发可以在构造函数之间传递参数,避免不必要的拷贝操作,从而提高性能。
  • (2) 可变参数模板函数:完美转发可以用于实现可接受任意数量和类型参数的函数,如实现一个通用的元组或 bind() 函数。
  • (3) 智能指针:完美转发在智能指针的实现中也有重要作用,例如 std::unique_ptrstd::shared_ptr 中的构造函数和 make() 函数等。
  • (4) 函数包装器:完美转发可以用于实现函数包装器,使包装后的函数能够正确处理所有类型的参数,包括右值引用。例如 std::function 的实现。
  • (5) 资源管理类:通过完美转发,可以使资源管理类(如锁管理类、线程池等)能够更方便地处理各种资源。

forward 完美转发的使用

  • 更改的核心代码
    • 更改 Allocator::construct() 函数,接收右值引用参数,并使用函数模板的类型推导 + 引用折叠 + forward 完美转发来识别左值类型和右值类型
    • 重载 Vector::push_back() 函数,接收右值引用参数,并使用函数模板的类型推导 + 引用折叠 + forward 完美转发来识别左值类型和右值类型
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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
#include <iostream>
#include <cstring>
#include "MyString.h"

using namespace std;

// 类模板
template<typename T>
// 空间配置器(负责内存开辟、内存释放、对象构造、对象析构)
struct Allocator {

// 数组的内存开辟
T* allocate(size_t size) {
return (T*)malloc(sizeof(T) * size);
}

// 数组的内存释放
void deallocate(void* p) {
free(p);
}

// 对象构造(接收右值引用参数)
// 基于函数模板的类型推导 + 引用折叠
// T & + Ty && = T &
// T && + Ty && = T &&
template<typename Ty>
void construct(T* p, Ty&& val) {
// 在指定的内存上构造对象(定位 new)
// forward 是完美转发,可以识别左值类型和右值类型
new(p)T(forward<Ty>(val));
}

// 对象析构
void destroy(T* p) {
// ~T() 代表了 T 类型对象的析构函数
p->~T();
}

};

// 类模板
template<typename T, typename Alloc = Allocator<T>>
// 向量容器
class Vector {

public:
// 构造函数
Vector(int size = 10) {
// 开辟数组的内存空间
_first = _allocator.allocate(size);
_last = _first;
_end = _first + size;
}

// 析构函数(先析构容器内的有效元素,然后再释放 _first 指针指向的堆内存)
~Vector() {
// 析构容器内的有效元素
for (T* p = _first; p != _last; p++) {
_allocator.destroy(p);
}

// 释放堆上的数组内存
_allocator.deallocate(_first);
_first = _last = _end = nullptr;
}

// 拷贝构造函数
Vector(const Vector<T>& v) {
// 容器的总大小
int size = v._end - v._first;
// 有效元素的个数
int length = v._last - v._first;

// 开辟数组的内存空间
_first = _allocator.allocate(size);

// 在指定的内存空间中构造对象
for (int i = 0; i < length; i++) {
_allocator.construct(_first + i, v._first[i]);
}
_last = _first + length;
_end = _first + size;
}

// 赋值运算符重载
Vector<T>& operator=(const Vector<T>& v) {
if (this == v) {
return *this;
}

// 析构容器内的有效元素
for (T* p = _first; p != _last; p++) {
_allocator.destroy(p);
}

// 释放堆上的数组内存
_allocator.deallocate(_first);

// 容器的总大小
int size = v._end - v._first;
// 有效元素的个数
int length = v._last - v._first;

// 开辟数组的内存空间
_first = _allocator.allocate(size);

// 在指定的内存空间中构造对象
for (int i = 0; i < length; i++) {
_allocator.construct(_first + i, v._first[i]);
}
_last = _first + length;
_end = _first + size;

return *this;
}

// 往容器尾部添加元素(接收右值引用参数)
// 基于函数模板的类型推导 + 引用折叠
// T & + Ty && = T &
// T && + Ty && = T &&
template<typename Ty>
void push_back(Ty&& val) {
if (full()) {
resize();
}
// 在指定的内存空间中构造对象
// forward 是完美转发,可以识别左值类型和右值类型
_allocator.construct(_last, forward<Ty>(val));
_last++;
}

// 从容器尾部删除元素(需要将对象的析构和内存释放分开处理)
void pop_back() {
if (!empty()) {
verify(_last - 1, _last - 1);
_last--;
// 在指定的内存空间中析构对象
_allocator.destroy(_last);
}
}

// 返回容器尾部的元素
T back() const {
if (empty()) {
throw "Vector is empty!";
}
return *(_last - 1);
}

// 容器是否满了
bool full() const {
return _last == _end;
}

// 容器是否为空
bool empty() const {
return _first == _last;
}

// 返回有效元素的个数
int size() const {
return _last - _first;
}

// 重载中括号运算符
T& operator[](int index) {
if (index < 0 || index >= size()) {
throw "OutOfRangeException";
}
return _first[index];
}

// 迭代器
class iterator {

public:

friend class Vector<T, Alloc>;

iterator(Vector<T, Alloc>* pvec = nullptr, T* p = nullptr) : _pVec(pvec), _ptr(p) {
// 维护迭代器的单向链表结构
Iterator_Base* itb = new Iterator_Base(this, _pVec->_head._next);
_pVec->_head._next = itb;
}

// 重载不等于运算符
bool operator!=(const iterator& other) const {
// 判断迭代器指向的容器是不是同一个
if (_pVec == nullptr || _pVec != other._pVec) {
throw "Iterator incompatable!";
}
return _ptr != other._ptr;
}

// 重载前置 ++ 运算符
iterator& operator++() {
// 检测迭代器的有效性
if (_pVec == nullptr) {
throw "Iterator invalid!";
}
_ptr++;
return *this;
}

// 重载后置 ++ 运算符
iterator operator++(int) {
// 检测迭代器的有效性
if (_pVec == nullptr) {
throw "Iterator invalid!";
}
return iterator(_ptr++);
}

// 解引用运算符重载
T& operator*() const {
// 检测迭代器的有效性
if (_pVec == nullptr) {
throw "Iterator invalid!";
}
return *_ptr;
}

private:
T* _ptr;
Vector<T, Alloc>* _pVec; // 当前迭代器是哪个容器的对象
};

// 返回的是容器底层首元素的迭代器的表示
iterator begin() {
return iterator(this, _first);
}

// 返回的是容器末尾元素后继位置的迭代器的表示
iterator end() {
return iterator(this, _last);
}

// 通过迭代器往容器插入元素
// 这里暂时不考虑容器扩容,也不考虑 it._prt 的指针合法性
iterator insert(iterator it, const T& val) {
verify(it._ptr - 1, _last);

// 重新分配数组的内存空间,并往右边移动数组元素
T* p = _last;
while (p > it._ptr) {
_allocator.construct(p, *(p - 1));
_allocator.destroy(p - 1);
p--;
}
_allocator.construct(p, val);

_last++;
return iterator(this, p);
}

// 通过迭代器往容器删除元素
iterator erase(iterator it) {
verify(it._ptr - 1, _last);

// 重新分配数组的内存空间,并往左边移动数组元素
T* p = it._ptr;
while (p < _last - 1) {
_allocator.destroy(p);
_allocator.construct(p, *(p + 1));
p++;
}
_allocator.destroy(p);

_last--;
return iterator(this, it._ptr);
}

private:
T* _first; // 指向数组起始的位置
T* _last; // 指向数组中有效元素的后继位置
T* _end; // 指向数组空间的后继位置
Alloc _allocator; // 定义容器空间配置器的对象

// 迭代器的单向链表结构
struct Iterator_Base {
Iterator_Base(iterator* cur = nullptr, Iterator_Base* next = nullptr) : _cur(cur), _next(next) {

}

iterator* _cur;
Iterator_Base* _next;
};

Iterator_Base _head;

// 扩容操作
void resize() {
int size = _end - _first;
// 开辟数组的内存空间
T* _ptemp = _allocator.allocate(size * 2);
// 在指定的内存空间中构造对象
for (int i = 0; i < size; i++) {
_allocator.construct(_ptemp + i, _first[i]);
}

// 析构原来容器内的有效元素
for (T* p = _first; p != _last; p++) {
_allocator.destroy(p);
}

// 释放原来的数组内存
_allocator.deallocate(_first);

_first = _ptemp;
_last = _first + size;
_end = _first + size * 2;
}

// 维护迭代器的单向链表
void verify(T* start, T* end) {
Iterator_Base* cur = &this->_head;
Iterator_Base* next = this->_head._next;
while (next != nullptr) {
if (next->_cur->_ptr >= start && next->_cur->_ptr <= end) {
// 迭代器失效,将迭代器持有的容器指针置为空
next->_cur->_pVec = nullptr;
// 在迭代器链表中,删除当前迭代器节点,并继续判断后面的迭代器节点是否失效
cur->_next = next->_next;
delete next;
next = cur->_next;
}
else {
next = next->_next;
}
}
}

};

int main() {
Vector<MyString> v1;
cout << "----------------------------------" << endl;
v1.push_back(MyString("bbb")); // 调用的是 MyString 带右值引用参数的拷贝构造函数
cout << "----------------------------------" << endl;
return 0;
}

在 Visual Studio 的 Debug 模式下,程序运行的输出结果如下:

1
2
3
4
5
6
----------------------------------
MyString(const char *p = nullptr)
MyString(MyString&& str)
~MyString()
----------------------------------
~MyString()