1.存在的意义
在使用一般指针编程的时候会出现的几种错误:
- 使用
new申请堆空间忘记delete
delete之前发生异常,不正常结束,跳过delete
- 当多线程使用指向同一个堆地址的指针,不知道本线程退出时是否应该
delete,以及是使用时否已经delete
2.通用的实现方法
使用一个类,类的成员变量中有一根指针ptr,类重载指针的操作:解引用*(),调用->,在析构的时候delete ptr。
存在的问题:多个智能指针指向同一个堆地址,第一个智能指针析构后,后面的指针析构时就会发生重复delete。
解决方案:
- 定义赋值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本。(赋值后指向的地址不再相同,失去指针意义)
- 建立所有权(ownership)概念,对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的构造函数会删除该对象。然后,让赋值操作转让所有权。这就是用于
auto_ptr和unique_ptr的策略,但unique_ptr的策略更严格。
- 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数(reference counting)。例如,赋值时,计数将加1,而指针过期时,计数将减1。仅当最后一个指针过期时,才调用delete。这是
shared_ptr采用的策略。
3.auto_ptr & unique_ptr
unique_ptr 可以传入new[] 返回的指针,在定义的时候需要加上[](只有unique_ptr可以使用new[] )(问题:shared_ptr vs2017中也可以使用new [])。如:
1 2
| unique_ptr<double[]> pad(new double[5]); pad[3] = 3.9;
|

auto_ptr是c++98标准中的(c++11弃用),unique_ptr是c++11标准中的。
auto_ptr 能够赋值给auto_ptr, 而unique_ptr不能赋值给unique_ptr(但是右值unique_ptr能够赋值给unique_ptr)。
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
| #include <iostream> #include <memory> #include <string> using namespace std;
unique_ptr<string> demo1(string s) { return unique_ptr<string> (new string(s)); }
auto_ptr<string> demo2(string s) { return auto_ptr<string>(new string(s)); }
int main() { string *s1 = new string("some words"); unique_ptr<string> up1(s1); unique_ptr<string> up2 = unique_ptr<string>(new string("some words")); unique_ptr<string> up3 = demo1("some words"); string *s2 = new string("some words"); auto_ptr<string> ap1(s2); auto_ptr<string> ap2 = ap1; auto_ptr<string> ap3 = auto_ptr<string>(new string("some words")); auto_ptr<string> ap4 = demo2("some words");
cout << *ap1 << endl; system("pause"); }
|
4. shared_ptr
shared_ptr中有两个指针:
- 构造时传入的指针
- 指向控制块的指针
在shared_ptr拷贝构造或者赋值的时候(如shared_ptr<int>a = b),a的控制块指针指向b的控制块,并将控制块中的引用次数+1(原子操作)。
由实现可以看出,共享多个shared_ptr,一定要是:
1 2 3 4 5 6 7 8
| int *num = new int(6); shared_ptr<int> old_ptr(num);
shared_ptr<int> new_ptr = old_ptr; shared_ptr<int> new_ptr(old_ptr);
shared_ptr<int> new_ptr<num>
|
line8中的构造方式,new_ptr和old_ptr的控制块并不是同一个,因此会发生多次析构的问题。
shared_ptr的循环引用问题
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
| #include <iostream> #include <memory> #include <string> using namespace std;
unique_ptr<string> demo1(string s) { return unique_ptr<string> (new string(s)); }
auto_ptr<string> demo2(string s) { return auto_ptr<string>(new string(s)); }
struct structA; struct structB;
struct structA { shared_ptr<structB> Aptr; ~structA() { cout << "deconstruct A" << endl; } };
struct structB { shared_ptr<structA> Bptr; ~structB() { cout << "deconstruct B" << endl; } };
int main() { { shared_ptr<structA> ap(new structA); { shared_ptr<structB> bp(new structB); cout << ap.use_count() << endl; cout << bp.use_count() << endl; cout << ap->Aptr.use_count() << endl; cout << bp->Bptr.use_count() << endl;
ap->Aptr = bp; bp->Bptr = ap; cout << endl;
cout << ap.use_count() << endl; cout << bp.use_count() << endl; cout << ap->Aptr.use_count() << endl; cout << bp->Bptr.use_count() << endl; } cout << endl; cout << ap.use_count() << endl; cout << ap->Aptr.use_count() << endl; }
system("pause"); }
|
- 可以看到,在39~40的操作之后,4个智能指针的引用计数器都为2。
- 在48行
bp析构的时候,因为Aptr还在指向structB,只有bp被析构,bp指向的structB并没有从堆中析构。
- 因为
structB没有从堆中析构,Bptr仍然指向structA,所以ap析构的时候,ap指向的structA也不会从堆中析构。
- 最后情况是堆中既有
structA,也有structB,他们的指针的引用计数都为1,内存泄露发生。
循环引用的避免:
- 将其中一个
shared_ptr改为weak_ptr(weak_ptr只是一种编译时的循环引用解决方案,如果运行时发生,依然会造成内存泄露)。
- 设计时避免循环引用
5. weak_ptr
使用较少,暂不探索
参考