CPP智能指针总结

1.存在的意义

在使用一般指针编程的时候会出现的几种错误:

  1. 使用new申请堆空间忘记delete
  2. delete之前发生异常,不正常结束,跳过delete
  3. 当多线程使用指向同一个堆地址的指针,不知道本线程退出时是否应该delete,以及是使用时否已经delete

2.通用的实现方法

使用一个类,类的成员变量中有一根指针ptr,类重载指针的操作:解引用*(),调用->,在析构的时候delete ptr

存在的问题:多个智能指针指向同一个堆地址,第一个智能指针析构后,后面的指针析构时就会发生重复delete

解决方案:

  1. 定义赋值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本。(赋值后指向的地址不再相同,失去指针意义)
  2. 建立所有权(ownership)概念,对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的构造函数会删除该对象。然后,让赋值操作转让所有权。这就是用于auto_ptrunique_ptr的策略,但unique_ptr的策略更严格。
  3. 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数(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; //像数组一样访问

pic1

auto_ptrc++98标准中的(c++11弃用),unique_ptrc++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 = up1; 编译不通过
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;
// 允许,但ap1已经失去s2的所有权
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中有两个指针:

  1. 构造时传入的指针
  2. 指向控制块的指针

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_ptrold_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");
}
/*
输出:
1
1
0
0

2
2
2
2

2
1
*/
  1. 可以看到,在39~40的操作之后,4个智能指针的引用计数器都为2。
  2. 在48行bp析构的时候,因为Aptr还在指向structB,只有bp被析构,bp指向的structB并没有从堆中析构。
  3. 因为structB没有从堆中析构,Bptr仍然指向structA,所以ap析构的时候,ap指向的structA也不会从堆中析构。
  4. 最后情况是堆中既有structA,也有structB,他们的指针的引用计数都为1,内存泄露发生。

循环引用的避免:

  1. 将其中一个shared_ptr改为weak_ptrweak_ptr只是一种编译时的循环引用解决方案,如果运行时发生,依然会造成内存泄露)。
  2. 设计时避免循环引用

5. weak_ptr

使用较少,暂不探索

参考