大纲 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 && d = 20 ; 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 (p)T (val); } void destroy (T *p) { 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; } ~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); } 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" )); 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 (p)T (val); } void construct (T* p, T&& val) { new (p)T (move (val)); } void destroy (T* p) { 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; } ~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 (); } _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); } 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" )); 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_ptr
和 std::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); } template <typename Ty> void construct (T* p, Ty&& val) { new (p)T (forward<Ty>(val)); } void destroy (T* p) { 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; } ~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 ; } template <typename Ty> void push_back (Ty&& val) { if (full ()) { resize (); } _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); } 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" )); cout << "----------------------------------" << endl; return 0 ; }
在 Visual Studio 的 Debug 模式下,程序运行的输出结果如下:
1 2 3 4 5 6 ---------------------------------- MyString(const char *p = nullptr) MyString(MyString&& str) ~MyString() ---------------------------------- ~MyString()