智能指针初探


最近在看《C++ primer》第五版,试图学习一点C++11的特性。因为在C++中动态数组需要new和delete,很容易出现忘记delete的情况,也有可能在尚有指针的情况下释放,导致引用非法的空指针,所以引入了智能指针。

shared_ptr

shared_ptr类和make_shared函数

  • 类似于vector,智能指针也是一种模板
  • 解引用一个智能指针的方式和普通指针类似

例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <memory>
#include <string>

using namespace std;

int main()
{
shared_ptr<string> p1 = make_shared<string>("");
if (p1 && p1->empty()) {
*p1 = "Hi";
}
cout << *p1 << endl;
}


shared_ptr 的拷贝和赋值

进行拷贝或赋值操作的时候,shared_ptr中的会有一个计数器来记录有多少个其他的shared_ptr指向相同的对象,和Java的引用计数法很相似

1
2
auto p = make_shared<int>(42) // p指向的对象只有一个引用者
auto q(p) //此时该对象有两个引用者

计数器变为0的时候,会自动释放所管理的对象,如下:

1
2
auto r = make_shared<int>(42) // p指向的对象只有一个引用者
r = q // 给r赋值,递增q指向的对象的引用计数,递减r指向对象的引用计数,r原来指向的对象没有引用者,自动释放

shared_ptr和new结合使用

1
2
shared_ptr<int> p1 = new int(1024);// 错误
shared_ptr<int> p2(new int(1024)); // 正确

一般情况下,一个用来初始化智能指针的普通指针必须指向动态内存。

不过不要混合使用普通指针和智能指针,原因如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <memory>
#include <string>

using namespace std;

void process(shared_ptr<int> ptr) {
// do nothing
}

int main()
{
int* x(new int(24));
cout << *x << endl;
process(shared_ptr<int>(x));
cout << *x << endl;
}

在这里面,进入process之后,由普通指针初始化的智能指针由于有一份拷贝了,引用计数为1,结束过程的时候,引用计数减1,变为0,释放内存,所以这段代码的结果如下:

x在process结束后就被释放了。

也不要用get获取指针然后来再来创建智能指针,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <memory>
#include <string>

using namespace std;

int main()
{
shared_ptr<int> p(new int(42));
int* q = p.get();
cout << *p << endl;
{
shared_ptr<int> x(q);
}
cout << *p << endl;
}

这样的结果和上面的类似,就是会访问被释放的内存。

reset

可以用reset操作来更新智能指针

1
p.reset(new int(1024));

这样就可以顺利地改变智能指针的值了,同时更新引用计数。

unique_ptr

与shared_ptr不同,unique_ptr在同一时刻只能有一个unique_ptr指向指定的对象。

u.release()方法可以放弃对指针的控制权并返回指针

u.reset()释放u指向的对象,

u.reset(q)/u.reset(nullptr)如果提供了普通指针q,令u指向找个对象,否则u置为空

weak_ptr

不控制所指向对象生存期的智能指针,与shared_ptr类似,但是不会受引用计数影响,也不会影响引用计数。

智能指针循环引用问题

参考了https://www.cnblogs.com/64open/p/4826765.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class B;
class A {
public:
shared_ptr<B> p;
};

class B {
public:
shared_ptr<A> p;
};

int main() {
while (true) {
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa->p = pb;
pb->p = pa;
// 这个地方两个指针的use_count()都为2
}
// 离开作用域后,销毁后user_count()为1.
return 0;
}

用weak_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
#include <iostream>
#include <memory>
#include <string>

using namespace std;
class B; // 前置声明
class A {
public:
shared_ptr<B> ptr;
};

class B {
public:
shared_ptr<A> ptr;
};

int main()
{
weak_ptr<A> test;
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa->ptr = pb;
pb->ptr = pa;
test = pa;
cout << test.use_count() << endl;
}
cout << test.use_count() << endl;
return 0;
}

两次cout分别为2和1,可以看出问题了。