智能指针(二) shared_ptr和weak_ptr

shared_ptr 内部包含两个指针,一个指向对象,另一个指向控制块,控制块中包含一个引用计数和其它一些数据。由于这个控制块需要在多个shared_ptr之间共享,即多个shared_ptr可以共享同一块内存,所以它也是存在于 heap 中的。

如果没有共享所有权的必要,就不必使用shared_ptr,而非unique_ptr

由于支持共享所有权,shared_ptr支持拷贝和赋值运算, 也支持移动。如果对象在创建的时候没有使用共享指针存储的话,之后也不能用共享指针管理这个对象了。

避免使用原生指针来创建shared_ptr指针

shared_ptr销毁对象的情况:

  1. 最后一个智能指针离开作用域
  2. 用其他的shared_ptr给一个shared_ptr初始化
  3. 最后一个智能指针调用 reset

shared_ptr的缺点:

  • 内存占用是原生指针的两倍,因为除了要管理一个原生指针外,还要维护一个引用计数

  • 使用了性能低的原子操作:考虑到线程安全问题,引用计数的增减必须是原子操作。 而原子操作一般情况下都比非原子操作慢

  • 两个shared_ptr可能出现循环引用,永远不能释放指向的对象

线程安全性

shared_ptr有两个成员,指向对象的指针ptr和管理引用计数的指针ref_count。引用计数本身是原子操作,是线程安全的,但 shared_ptr的赋值操作由复制对象指针和修改使用计数两个操作复合而成, 因此仍不是线程安全的。如果要从多个线程读写同一个shared_ptr 对象,还是需要加锁。

陈硕专门写了这篇文章分析这个问题,
也可以看我自己的这篇文章,子线程里能写shared_ptr指向的对象,回到主线程就变了。

尽量使用 make_shared()

为了节省一次内存分配,原来shared_ptr<Foo> x(new Foo); 需要为 Foo 和 ref_count 各分配一次内存,现在用make_shared()的话,可以一次分配一块足够大的内存,供 Foo 和 ref_count 对象容身。

常见用法

1
2
3
4
5
6
7
8
    int num = 12;
int* p = new int(num);
// shared_ptr<int> p1 = &num; // error
shared_ptr<int> p2 = boost::make_shared<int>(num);
shared_ptr<Foo> p3(p);

// cout << *p2 <<endl;
// cout << *p3 <<endl;

p1的用法是错的,p2和p3正确,但是不要同时使用,改用p3(p2)即可

1
2
3
boost::shared_ptr<Foo> a;
cout<< a.use_count()<<endl;
a->out();

执行a->out()会报错,原因是a没有指向对象,应该这样定义:boost::shared_ptr<Foo> a(new Foo());

再看这样的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
boost::shared_ptr<Foo> a(new Foo());            // 让a指向对象
cout<< a.use_count()<<endl; // 1

boost::shared_ptr<Foo> b = a; // 另一个指针也指向同一个对象
cout<< a.use_count()<<endl; // 2
cout<< b.use_count()<<endl; // 2

a.reset(); // 不执行析构函数,实际执行 delete a; a = NULL;
a->out(); // 报错
b->out(); // 正常
cout<< a.use_count()<<endl; // 0
cout<< b.use_count()<<endl; // 1
b.reset(); // 执行析构函数
cout<<"end"<<endl;

只有对象的引用计数为0的时候,才执行类的析构和free其内存:

1
2
3
4
5
6
7
8
9
boost::shared_ptr<Foo> a(new Foo());
boost::shared_ptr<Foo> b = a;

cout<<a<<endl;
cout<<b<<endl;

a.reset(new Foo()); // a重新初始化,指向另一个地址
cout<<a<<endl;
cout<<b<<endl;

运行结果:

1
2
3
4
5
0x99fc20
0x99fc20

0x9a0c70
0x99fc20

不要把一个原生指针给多个shared_ptr管理

1
2
3
int* ptr = new int;
boost::shared_ptr<int> p1(ptr);
boost::shared_ptr<int> p2(ptr);

这样做会导致ptr会被释放两次。在实际应用中,保证除了第一个shared_ptr使用ptr定义之外,后面的都采用p1来操作,就不会出现此类问题。

可以在标准容器里存储boost::shared_ptr,但不能存储std::auto_ptrboost::scoped_ptr,后两者不能共享对象所有权.

1
2
3
std::vector<boost::shared_ptr<int> > v; 
v.push_back(boost::shared_ptr<int>(new int(1)));
v.push_back(boost::shared_ptr<int>(new int(2)));

自定义删除器

默认情况下,shared_ptr调用delete()函数进行资源释放,即delete p;。但是如果shared_ptr指向一个数组而不是一个简单的指针,应该调用delete[] p,此时可以将一个回调传递给shared_ptr的构造函数来定制删除器。

主要是利用了构造函数template<class Y, class D> shared_ptr(Y * p, D d);,第一个参数是要被管理的指针, 与其它形式的构造函数一致; 第二个参数称为删除器, 他是一个接受Y*的可调用物, d(p)的行为应类似与delete p, 而且不应该抛出异常。有了删除器, 我们就可以管理一些更复杂的资源, 比如数据库连接, socket连接。

其实有很多种用法,只列举常用的普通函数法

1
2
3
4
5
6
7
8
9
Derived *d = new Derived[5];
boost::shared_ptr<Derived> p1(d, deleter);

// deleter函数定义
void deleter(Derived* d)
{
delete[] d;
cout<<"delete"<<endl;
}

参考:官方说明
boost智能指针之shared_ptr