本节分为七个部分:
- 学习复数类 Complex
- 模拟实现 C++ 的 string 类
- string 字符串对象的迭代器
- vector 容器的迭代器 iterator 实现
- 什么是容器的迭代器失效问题
- new 和 delete 重载实现的对象池
- 深入理解 new 和 delete 的原理
什么是运算符重载
运算符重载实质还是一个 函数。
通过重载运算符,可以让类在一些场景下使用起来更加方便。
语法
1
| 返回值类型 operator op (参数);
|
示例:
1
| ClassType& operator= (const ClassType& src);
|
1. 学习复数类 Complex
复数是形如 a+b
的数,复数由实部和虚部构成,在 C++ 的模板库中由 complex 类,可以直接调用,包含在complex头文件中,再使用时应该添加 #include<complex>
。下面介绍一些基本操作
1. 生成复数对象
complex 类型的构造函数接受两个参数,第一个参数是复数实部的值,第二个参数是虚部的值。要想生成一个复数对象,并且对其值进行修改,参考以下代码:
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
| #include <iostream> #include <complex> using namespace std;
int main() { complex<double>x1(2, 3); complex<double>x2; complex<double>x3(x1);
cout << "x1 = " << x1 << endl; cout << "x2 = " << x2 << endl; cout << "x3 = " << x3 << endl;
x1.real(22); x1.imag(33);
cout << "x1 = " << x1 << endl;
complex<double>a, b, c; cout << "请输入三个复数:"; cin >> a >> b >> c; cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; return 0; }
|
2. 复数的运算
复数和实数一样都有加减乘除四则运算,这些运算符号在 complex 模板中已经被重载过,能够直接使用,下面代码示例展示了其功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <iostream> #include <complex> using namespace std;
int main() { complex<double> z(2, 3); complex<double> z1 = z + 5.0; cout << z1 << endl; complex<double> z2 = z - 5.0; cout << z2 << endl; complex<double> z3 = z * 2.0; cout << z3 << endl; complex<double>z4 = z / complex<double>(1, 1); cout << z4 << endl;
return 0; }
|
3. 复数的比较
复数的比较需要同时比较实部和虚部是否都相等,有其一不等两数就 不等,下面是示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <iostream> #include <complex> using namespace std;
int main() { complex<double> z1(2, 3); complex<double> z2(3, 4); complex<double> z3(2, 3);
cout << boolalpha << (z1 == z3) << endl; cout << boolalpha << (z1 == z2) << endl;
return 0; }
|
4. 实现 Complex 类
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
| #include <iostream>
using namespace std;
template<typename T> class Complex { public: Complex() : real(0) , imag(0) { } Complex(T re, T im) : real(re) , imag(im) { } Complex(const Complex<T>& com) : real(com.real) , imag(com.imag) { } ~Complex() { }
void printComplex() { cout << "(" << real << ", " << imag << ")" << endl; } Complex& operator+(const Complex<T>& com) { this->real += com.real; this->imag += com.imag; return *this; } Complex& operator-(const Complex<T>& com) { this->real -= com.real; this->imag -= com.imag; return *this; } Complex& operator*(const Complex<T>& com) { this->real = (real * com.real) - (imag * com.imag); this->imag = (real * com.real) + (imag * com.imag); return *this; } Complex& operator/(const Complex<T>& com) { this->real = (real * com.real + imag * com.imag) / (com.real * com.real + com.imag * com.imag); this->real = (real * com.real - imag * com.imag) / (com.real * com.real + com.imag * com.imag); return *this; }
private: T real; T imag; };
int main() { Complex<double> c1(2.0, 1.0); c1.printComplex(); Complex<double> c2(1.0, 3.0); c2.printComplex(); cout << "----------------" << endl;
c1 = c1 + c2; cout << "c1 加法后:"; c1.printComplex(); cout << "----------------" << endl;
c2 = c2 - c1; cout << "c2 减法后:"; c2.printComplex(); cout << "----------------" << endl;
c1 = c2 * c1; cout << "c1 乘法后:"; c1.printComplex(); cout << "----------------" << endl;
c2 = c2 / c1; cout << "c2 除法后:"; c2.printComplex();
return 0; }
|
另一种实现方式:
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
| #include <iostream> using namespace std; class CComplex { public: CComplex(int r = 0, int i = 0) :_mreal(r) ,_mimage(i) { } void operator=(const CComplex&obj) { this->_mreal = obj._mreal; this->_mimage = obj._mimage; }
CComplex operator++(int) { return CComplex(this->_mreal++, this->_mimage++);
} CComplex& operator++() { _mreal += 1; _mimage += 1; return *this; } void operator+=(const CComplex&rhs) { this->_mreal += rhs._mreal; this->_mimage += rhs._mimage; } void show() { cout << "real:" << _mreal << "image:" << _mimage << endl; } private: int _mreal; int _mimage; friend CComplex operator+(const CComplex &lhs, const CComplex &rhs); friend ostream& operator<<(ostream&out, const CComplex&src); friend istream& operator>>(istream&in, CComplex&src); }; istream& operator>>(istream&in, CComplex&src) { int a, b; in >> a >> b; src._mreal = a; src._mimage = b; return in; } ostream& operator<<(ostream&out, const CComplex&src) { out << "real:" << src._mreal << "image:" << src._mimage << endl; return out; } CComplex operator+(const CComplex &lhs, const CComplex &rhs) { return CComplex(lhs._mreal + rhs._mreal, lhs._mimage + rhs._mimage); }
int main() { CComplex c1(1, 2); CComplex c2(2, 3); CComplex c4; c4 = c1+c2; c4.show(); c4 = c1 + 20; c4.show(); c4 = 30 + c2; c4.show(); CComplex c5; c5 = c4++; c5.show(); c5 = ++c4; c5.show(); c5 += c4; c5.show(); cout << "++++++++++++++++++++++++++++++" << endl; cout << c5; CComplex c6; cin >> c6; cout << c6; return 0; }
|
2. 模拟实现 C++ 的 string 类
string 类中的基本方法有:
- 构造函数
-
<<
运算符重载
运算符重载
- len 方法
[]
运算符重载
- 迭代器实现
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
| #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> using namespace std;
int main() { string str1; string str2 = "aaa"; string str3 = "bbb";
string str4 = str2 + str3; string str5 = str2 + "ccc"; string str6 = "ddd" + str2;
cout << "str6:" << str6 << endl;
if (str5 > str6) { cout << str5 << " > " << str6 << endl; } else { cout << str5 << " < " << str6 << endl; }
int len = str6.length(); for (int i = 0; i < len; i++) { cout << str6[i] << " "; } cout << endl;
char buff[1024] = { 0 }; strcpy(buff, str6.c_str()); cout << "buff:" << buff << endl;
string::iterator it = str2.begin(); for (it = str2.begin(); it != str2.end(); ++it) { cout << (*it) << " "; }
return 0; }
|
1. 实现 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 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
| #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <ostream> using namespace std;
class String { public: String(const char* p = nullptr) { if (p != nullptr) { _pstr = new char[strlen(p) + 1]; strcpy(_pstr, p); } else { _pstr = new char[1]; *_pstr = '\0'; } } String(const String& str) { _pstr = new char[strlen(str._pstr) + 1]; strcpy(_pstr, str._pstr); } ~String() { delete[] _pstr; _pstr = nullptr; }
String& operator=(const String& str) { if (this == &str) { return *this; } delete[] _pstr;
_pstr = new char[strlen(str._pstr) + 1]; strcpy(_pstr, str._pstr); return *this; } bool operator>(const String& str) const { return strcmp(_pstr, str._pstr) > 0; } bool operator<(const String& str) const { return strcmp(_pstr, str._pstr) < 0; } bool operator==(const String& str) const { return strcmp(_pstr, str._pstr) == 0; } char& operator[](int index) { return _pstr[index]; } const char& operator[](int index) const { return _pstr[index]; }
int length() { return strlen(_pstr); } const char* c_str() const { return _pstr; }
private: char* _pstr;
friend ostream& operator<<(ostream& out, const String& str);
friend String operator+(const String& lhs, const String& rhs);
};
ostream& operator<<(ostream& out, const String& str) { out << str._pstr; return out; };
String operator+(const String& lhs, const String& rhs) { char* _ptmp = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1]; strcpy(_ptmp, lhs._pstr); strcat(_ptmp, rhs._pstr);
String tmp(_ptmp); delete[]_ptmp; return tmp; };
int main() { String str1; String str2 = "aaa"; String str3 = "bbb";
String str4 = str2 + str3; String str5 = str2 + "ccc"; String str6 = "ddd" + str2;
cout << "str6:" << str6 << endl;
if (str5 > str6) { cout << str5 << " > " << str6 << endl; } else { cout << str5 << " < " << str6 << endl; }
int len = str6.length(); for (int i = 0; i < len; i++) { cout << str6[i] << " "; } cout << endl;
char buff[1024] = { 0 }; strcpy(buff, str6.c_str()); cout << "buff:" << buff << endl;
return 0; }
|
存在的问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| String operator+(const String& lhs, const String& rhs) { char* _ptmp = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1]; strcpy(_ptmp, lhs._pstr); strcat(_ptmp, rhs._pstr);
String tmp(_ptmp); delete[]_ptmp; return tmp; };
|
为了将局部新开辟的内存空间 _ptmp
资源释放,我们后面又构造了一个临时对象 String tmp(_ptmp)
,之后将局部变量资源释放 delete[]_ptmp
,但是这样一来的效率非常低!
3. string 字符串对象的迭代器
容器的迭代器可以透明的访问容器内部的元素的值
1 2 3 4
| string::iterator it = str2.begin(); for (it = str2.begin(); it != str2.end(); ++it) { cout << (*it) << " "; }
|
迭代器代码:
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
| class iterator { public: iterator(char *p = nullptr) : _p(p) { } iterator(const iterator& iter) : _p(iter._p) { } bool operator!=(const iterator& it) { return _p != it._p; } iterator& operator++() { ++_p; return *this; } iterator operator++(int) { iterator tmp(*this); _p++; return tmp; } char& operator*() { return *_p; }
private: char* _p; };
iterator begin() { return iterator(_pstr); } iterator end() { return iterator(_pstr + length()); }
|
完整代码:
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
| #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <ostream> using namespace std;
class String { public: class iterator { public: iterator(char *p = nullptr) : _p(p) { } iterator(const iterator& iter) : _p(iter._p) { } bool operator!=(const iterator& it) { return _p != it._p; } iterator& operator++() { ++_p; return *this; } iterator operator++(int) { iterator tmp(*this); _p++; return tmp; } char& operator*() { return *_p; }
private: char* _p; };
iterator begin() { return iterator(_pstr); } iterator end() { return iterator(_pstr + length()); }
String(const char* p = nullptr) { if (p != nullptr) { _pstr = new char[strlen(p) + 1]; strcpy(_pstr, p); } else { _pstr = new char[1]; *_pstr = '\0'; } } String(const String& str) { _pstr = new char[strlen(str._pstr) + 1]; strcpy(_pstr, str._pstr); } ~String() { delete[] _pstr; _pstr = nullptr; }
String& operator=(const String& str) { if (this == &str) { return *this; } delete[] _pstr;
_pstr = new char[strlen(str._pstr) + 1]; strcpy(_pstr, str._pstr); return *this; } bool operator>(const String& str) const { return strcmp(_pstr, str._pstr) > 0; } bool operator<(const String& str) const { return strcmp(_pstr, str._pstr) < 0; } bool operator==(const String& str) const { return strcmp(_pstr, str._pstr) == 0; } char& operator[](int index) { return _pstr[index]; } const char& operator[](int index) const { return _pstr[index]; }
int length() { return strlen(_pstr); } const char* c_str() const { return _pstr; }
private: char* _pstr;
friend ostream& operator<<(ostream& out, const String& str);
friend String operator+(const String& lhs, const String& rhs);
};
ostream& operator<<(ostream& out, const String& str) { out << str._pstr; return out; };
String operator+(const String& lhs, const String& rhs) { char* _ptmp = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1]; strcpy(_ptmp, lhs._pstr); strcat(_ptmp, rhs._pstr);
String tmp(_ptmp); delete[]_ptmp; return tmp; };
int main() { String str1; String str2 = "aaa"; String str3 = "bbb";
String str4 = str2 + str3; String str5 = str2 + "ccc"; String str6 = "ddd" + str2;
cout << "str6:" << str6 << endl;
if (str5 > str6) { cout << str5 << " > " << str6 << endl; } else { cout << str5 << " < " << str6 << endl; }
int len = str6.length(); for (int i = 0; i < len; i++) { cout << str6[i] << " "; } cout << endl;
char buff[1024] = { 0 }; strcpy(buff, str6.c_str()); cout << "buff:" << buff << endl;
String::iterator it = str2.begin(); for (it = str2.begin(); it != str2.end(); ++it) { cout << (*it) << " "; }
for (char ch : str2) { cout << ch << " "; }
return 0; }
|
1. 代码优化
带右值引用参数的拷贝构造与带右值引用参数的赋值重载函数
考虑这样一个操作:
由于问题场景的特殊,子函数调用时我们无法返回一个临时对象。
而且我们也只能用赋值的方式接收一个函数调用的返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| String GetString(const String&str) { std::cout << "\n GetString() begin\n"; String tmp(str.c_str()); std::cout << "\n GetString() end\n"; return tmp; } int main() { String s1 = "123"; String s2; std::cout << "\n main call GetString() begin\n"; s2 = GetString(s1); std::cout << "\n main call GetString() end\n"; return 0; }
|
存在的问题:
(1)在调用 return tmp;
时 实际是使用构造一个临时对象,临时对象调用拷贝构造函数从 tmp 中复制数据,然后调用析构函数析构 tmp 对象,这样就白耗费资源了,tmpStr 资源不要就早说,直接给函数栈帧上临时对象就好了;
(2)s2 调用赋值运算符 :临时量给 str2 赋值,str2 是原本已经存在的对象,它也有一个指针 mptr,原先也指向了一个空间。
对于赋值来说,排除自赋值,然后把原先指向的空间释放掉,然后按照 str 的尺寸开辟空间,然后拷贝数据进行来。即按照临时对象的字符串大小开辟空间,然后把数据一个一个拷贝进来。
然后出语句,把这个临时对象析构及它指向的堆内存空间释放掉。
过程过于复杂,为什么不能直接把临时对象的外部资源给 str2 不就完了吗?
2. 右值引用
1 2 3
| String &&e = String("aaa"); const String &e = MyString("aaa");
|
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
| String(const String& str) { if (str._pstr !=nullptr) { _pstr = new char[strlen(str._pstr) + 1]; strcpy(_pstr, str._pstr); } std::cout << "String 左值拷贝构造函数\n"; } String(String &&str) { std::cout << "String 右值拷贝构造函数\n"; _pstr = str._pstr; str._pstr = nullptr; }
String& operator=(const String& src) { std::cout << "String 左值赋值运算符\n"; if (this == &src) return *this; if (src._pstr == nullptr) { return *this; } if (_pstr) { delete[]_pstr; } _pstr = new char[strlen(src._pstr) + 1]; strcpy(_pstr, src._pstr); return *this; } String& operator=(String &&str) { std::cout << "String 右值引用参数的赋值重载函数\n"; if (this == &str) return *this; if (str._pstr == nullptr) { return *this; } delete[]_pstr; char * tmp=_pstr ; _pstr = str._pstr; str._pstr = tmp; return *this; }
|
通过右值引用,没有任何内存的开辟和释放和数据的拷贝
右值引用前:
右值引用后:
4. vector 容器的迭代器 iterator 实现
问题 1:删除 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
| #include <iostream> #include <vector> using namespace std;
int main() { vector<int> vec; int i = 0; for (i = 0; i < 10; i++) { vec.push_back(i); }
auto it = vec.begin(); for (; it < vec.end(); it++) { if ((*it) % 2 == 0) { vec.erase(it); } }
return 0; }
|
运行导致程序崩溃!
问题 2: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
| #include <iostream> #include <vector> using namespace std;
int main() { vector<int> vec; int i = 0; for (i = 0; i < 10; i++) { vec.push_back(i); }
vector<int>::iterator it = vec.begin(); for (; it < vec.end(); it++) { if ((*it) % 2 == 0) { vec.insert(it, *it - 1); } }
return 0; }
|
运行导致程序崩溃!
问题 3:push_back 触发扩容时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <iostream> #include <vector> using namespace std;
int main() { vector<int> vec; vec.push_back(1); vec.push_back(2); vector<int>::iterator it = vec.begin();
cout << "it = " << *it << endl; cout << "容量:" << vec.capacity() << endl;
vec.push_back(3); vec.push_back(4);
cout << "后push_back容量:" << vec.capacity() << endl; cout << "添加后,it = " << *it << endl;
return 0; }
|
原因:iterator 失效
当删除(获取增加)it 位置的元素时,导致 it 后面的迭代器全部失效。因此多次调用 erase\insert
导致崩溃
5. 什么是容器的迭代器失效问题
1. 原因
问题一:
当容器调用 erase 时,当前位置到容器末尾元素的所有的迭代器全部失效
问题二:
当容器调用 insert 时,当前位置到容器末尾元素的所有的迭代器全部失效
当容器调用 insert 时,如果引起容器内存扩容,原来容器的所有的迭代器就全部失效
问题三:
当容器 push_back 时,如果当前容量不足,则会触发扩容,导致整个容器重新申请内存,并且将原有的数据复制到新内存中将原有内存释放,这自然是会导致迭代器失效的,因为迭代器所指的内存都已经被释放。
2. 解决
进行更新操作:erase\insert
后会返回指向下一个元素的迭代器
从向量中删除单个元素(位置)或一系列元素([第一、最后一个]
)。
这有效地减少了容器的大小,减少了被删除的元素的数量,这些元素会被销毁。
由于向量使用数组作为其底层存储,擦除向量端以外位置的元素会导致容器在段擦除后将所有元素重新定位到其新位置。与其他类型的序列容器对相同操作执行的操作相比,这通常是一种低效的操作(如列表或转发列表)。
同理,insert
通过在指定位置的元素之前插入新元素来扩展向量,从而通过插入的元素数量有效地增加容器大小。
当且仅当新向量大小超过当前向量容量时,这会导致自动重新分配分配分配的存储空间。
因为向量使用数组作为其底层存储,所以在向量末端以外的位置插入元素会导致容器将位置之后的所有元素重新定位到它们的新位置。与其他类型的序列容器(如 list 或 forward_list)对相同操作执行的操作相比,这通常是一种低效的操作。
这些参数确定插入的元素数量及其初始化值:
也说明了进行插入操作会导致之后的迭代器失效。
修改代码:
erase 解决代码:
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
| #include <iostream> #include <vector> using namespace std;
int main() { vector<int> vec; int i = 0; for (i = 0; i < 10; i++) { vec.push_back(i); }
auto it = vec.begin(); while (it != vec.end()) { if ((*it) % 2 == 0) { it = vec.erase(it); } else { it++; } }
for (auto it : vec) { cout << it << " "; }
return 0; }
|
insert 解决代码:
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
| #include <iostream> #include <vector> using namespace std; int main() { vector<int> vec; for (int i = 0; i < 10; ++i) { vec.push_back(i); } auto it = vec.begin(); for (; it != vec.end(); it++) { if ((*it) % 2 == 0) { it = vec.insert(it, *it - 1); it++; } } for (auto val : vec) { cout << val << " "; } return 0; }
|
3. vector 实现中的 insert 和 erase
头插法:
检查迭代器失效:
在进行删除或增加的时候,要检测该位置到 last 位置,使其迭代器失效
1 2 3 4 5 6 7 8 9 10 11 12
| void pop_back() { if (empty()) return; verify(Last-1,Last); --Last; _allocator.destroy(Last); }
|
迭代器检查实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void verify(T* first, t* last) { Iterator_Base* pre = &this->head; Iterator_Base* it = &this->head._next;
while (it != nullptr) { if (it->_cur->_ptr > first && it->_cur->_ptr <= last) { it->_cur->_pVec = nullptr; pre->_next = it->_next; delete it; it = pre->_next; } else { pre = it; it = it->_next; } } }
|
6. new 和 delete 重载实现的对象池
在编程中,我们经常会涉及到对象的操作,而经常的操作模式如下图所示:创建对象
->使用对象
->销毁对象
。
而这个对象有可能创建的时候会需要构建很多资源,消耗比较大, 比如:在hiredis
的SDK中每次都创建一个redisContext
,如果需要查询,那就首先要进行网络连接。如果一直都是上图的工作方式,那将会频繁的创建连接,查询完毕后再释放连接。重新建立连接,让网络的查询效率降低。
这个时候就可以构建一个对象池
来重复利用这个对象,并且一般要做到线程安全:
- 从
对象池
中获取对象,如果没有对象,则创建一个,并返回
- 使用对象
- 使用完成对象后,将对象还回
对象池
那么符合如下条件的,应该适合使用对象池
技术:
- 有一些对象虽然创建开销比较大,但是不一定能够重复使用。要使用
对象池
一定要确保对象能够重复使用。
- 这个对象构建的时候,有一些耗时的资源可以重复利用。比如
redisContext
的网络连接。又或者如果对象的频繁申请释放会带来一些其他的资源使用问题,比如内存碎片
。重复利用能够提升程序的效率。
对象池
的数量应该控制在能够接受的范围内,并不会无限膨胀。
1. 对象池实现原理
- 分配过程:
我们首先申请比如 100000 块内存空间,用这个块类型的指针指向这个申请好内存的首地址。如图:这个块可以是一个链式队列的一个节点或者一个树的节点等。
当我们要分配给用户一块空间的时候我们就可以,先将 _itemPool
的地址保存到临时指针 p,然后再 _itemPool++
的操作,然后再将 p 返回给用户即可。
- 释放过程:
比如现在的内存分配状况如图所示:现在用户需要归还第 1 块内存。
我们首先要归还块的首地址指向 _itemPool
的下一个块,然后再将 _itemPool
指向归还的块的地址。即可
2. 以队列实现一个简单对象池
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
| #include <iostream>
using namespace std;
template<typename T> class Queue { public: Queue() { _front = _rear = new QueueItem(); } ~Queue() { QueueItem* cur = _front; while (cur != nullptr) { _front = cur->_next; delete cur; cur = _front; } } Queue(const Queue& obj) = delete;
void operator=(const Queue& obj) = delete;
void push(const T& val) { QueueItem* item = new QueueItem(val); _rear->_next = item; _rear = item; } void pop() { if (empty()) { return; } QueueItem* first = _front->_next; _front->_next = first->_next; if (_front->_next == nullptr) { _rear = _front; } delete first; } T front() const { return _front->_next->_data; } bool empty()const { return _rear == _front; }
struct QueueItem { QueueItem(T data = T()) :_data(data), _next(nullptr) {}
void* operator new(size_t size) { if (_itemPool == nullptr) { _itemPool = (QueueItem*)new char[POOL_ITEM_SIZE * sizeof(QueueItem)]; QueueItem* p = _itemPool; for (; p != _itemPool + POOL_ITEM_SIZE - 1; ++p) { p->_next = p + 1; } p->_next = nullptr; } QueueItem* p = _itemPool; _itemPool = _itemPool->_next; return p; } void operator delete(void* ptr) { QueueItem* p = (QueueItem*)ptr; p->_next = _itemPool; _itemPool = p; }
static QueueItem* _itemPool; static const int POOL_ITEM_SIZE = 1000000;
T _data; QueueItem* _next; };
private: QueueItem* _front; QueueItem* _rear; };
template<typename T> typename Queue<T>::QueueItem* Queue<T>::QueueItem::_itemPool = nullptr; int main() { Queue<int> que; for (int i = 0; i < 1000000; ++i) { que.push(i); que.pop(); }
cout << que.empty() << endl; return 0; }
|
7. 深入理解 new 和 delete 的原理
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
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
|
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) { void* p; while ((p = malloc(size)) == 0) if (_callnewh(size) == 0) { static const std::bad_alloc nomem; _RAISE(nomem); } return (p); }
void operator delete(void* pUserData) { _CrtMemBlockHeader* pHead; RTCCALLBACK(_RTC_Free_hook, (pUserData, 0)); if (pUserData == NULL) return; _mlock(_HEAP_LOCK); __TRY pHead = pHdr(pUserData); _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse)); _free_dbg(pUserData, pHead->nBlockUse); __FINALLY _munlock(_HEAP_LOCK); __END_TRY_FINALLY return; }
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
|
1. 内置类型:
如果申请的是内置类型的空间 new
和 malloc
,delete
和 free
基本类似,不同的地方是:new/delete
申请和释放的是单个元素的空间,new[]
和 delete[]
申请的是连续空间,而且 new 在申请空间失败时会抛异常,malloc会返回NULL。
2. 自定义类型:
new的原理:
- 调用operator new函数申请空间
- 在申请的空间上执行构造函数,完成对象的构造
delete的原理:
在空间上执行析构函数,完成对象中资源的清理工作在空间上执行析构函数,完成对象中资源的清理工作
new T[N]
的原理:
- 调用
operator new[]
函数,在 operator new[]
中实际调用 operator new
函数完成 N 个对象空间的申请
- 在申请的空间上执行N次构造函数
delete[ ]
的原理:
- 在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理
- 调用
operator delete[]
释放空间,实际在 operator delete[]
中调用 operator delete
来释放空间