shared_ptr的原理与应用(二)
red_ptr的使用之前,我们首先来看看它的基本实现原理。
刚才说到,当多个shared_ptr管理同一个指针,仅当最后一个shared_ptr析构时,指针才被delete。这是怎么实现的呢?答案是:引用计数(reference counting)。引用计数指的是,所有管理同一个裸指针(raw pointer)的shared_ptr,都共享一个引用计数器,每当一个shared_ptr被赋值(或拷贝构造)给其它shared_ptr时,这个共享的引用计数器就加1,当一个shared_ptr析构或者被用于管理其它裸指针时,这个引用计数器就减1,如果此时发现引用计数器为0,那么说明它是管理这个指针的最后一个shared_ptr了,于是我们释放指针指向的资源。
在底层实现中,这个引用计数器保存在某个内部类型里(这个类型中还包含了deleter,它控制了指针的释放策略,默认情况下就是普通的delete操作),而这个内部类型对象在shared_ptr第一次构造时以指针的形式保存在shared_ptr中。shared_ptr重载了赋值运算符,在赋值和拷贝构造另一个shared_ptr时,这个指针被另一个shared_ptr共享。在引用计数归零时,这个内部类型指针与shared_ptr管理的资源一起被释放。此外,为了保证线程安全性,引用计数器的加1,减1操作都是原子操作,它保证shared_ptr由多个线程共享时不会爆掉。
这就是shared_ptr的实现原理,现在我们来看看怎么用它吧!(超简单)
std::shared_ptr位于头文件中(这里只讲C++11,boost的shared_ptr当然是放在boost的头文件中),下面我以代码示例的形式展现它的用法,具体文档可以看这里。
// 初始化
shared_ptr x = shared_ptr(new int); // 这个方法有缺陷,下面我会说
shared_ptr y = make_shared();
shared_ptr obj = make_shared(arg1, arg2); // arg1, arg2是Resource构造函数的参数
// 赋值
shared_ptr z = x; // 此时z和x共享同一个引用计数器
// 像普通指针一样使用
int val = *x;
assert (x == z);
assert (y != z);
assert (x != nullptr);
obj->someMethod();
// 其它辅助操作
x.swap(z); // 交换两个shared_ptr管理的裸指针(当然,包含它们的引用计数)
obj.reset(); // 重置该shared_ptr(引用计数减1)
太好用了!
错误用法1:循环引用
shared_ptr的一个最大的缺点,或者说,引用计数策略最大的缺点,就是循环引用(cyclic reference),下面是一个典型的事故现场:
class Observer; // 前向声明
class Subject {
private:
std::vector> observers;
public:
Subject() {}
addObserver(shared_ptr ob) {
observers.push_back(ob);
}
// 其它代码
..........
};
class Observer {
private:
shared_ptr object;
public:
Observer(shared_ptr
// 其它代码
...........
};
目标(Subject)类连接着多个观察者(Observer)类,当某个事件发生时,目标类可以遍历观察者数组observers,对每个观察者进行"通知",而观察者类中,也保存着目标类的shared_ptr,这样多个观察者之间可以以目标类为桥梁进行沟通,除了会发生内存泄漏以外,这是很不错的设计模式嘛!等等,不是说用了shared_ptr管理资源后就不会内存泄漏了吗?怎么又漏了?
这就是引用计数模型失效的唯一的情况:循环引用。循环引用指的是,一个引用通过一系列的引用链,竟然引用回自身,上面的例子中,Subject->Observer->Subject就是这么一条环形的引用链。假设我们的程序中只有一个变量shared_ptr p,此时,p指向的对象不仅通过该shared_ptr引用自己,还通过它包含的Observer中的object成员变量引用回自己,于是它的引用计数是2,每个Observer的引用计数都是1。当p析构时,它的引用计数减1,变成2-1=1(大于0!),p指向对象的析构函数将不会被调用,于是p和它包含的每个Observer对象在程序结束时依然驻留在内存中没被delete,形成内存泄漏。
weak_ptr
为了解决这一问题,标准库提供了std::weak_ptr(弱引用),它也位于中。
weak_ptr是shared_ptr的"观察者",它与一个shared_ptr绑定,但却不参与引用计数的计算,在需要时,它还能摇身一变,生成一个与它所"观察"的shared_ptr共享引用计数器的新shared_ptr。总而言之,weak_ptr的作用就是:在需要时变出一个shared_ptr,在其它时候不干扰shared_ptr的引用计数。
在上面的例子中,我们只需简单地将Observer中object成员的类型换成std::weak_ptr即可解决内存泄漏的问题,此刻(接着上面的例子),p指向对象的引用计数为1,所以在p析构时,Subject指针将被delete,其中包含的observers数组在析构时,内部的Observer对象的引用计数也将变为0,故它们也被delete了,资源释放得干干净净。
下面,是weak_ptr的使用方法:
std::shared_ptr sh = std::make_shared();
// 用一个shared_ptr初始化
std::weak_ptr w(sh);
// 变出shared_ptr
std::shared_ptr another = w.lock();
// 判断weak_ptr所观察的shared_ptr的资源是否已经释放
bool isDeleted = w.expired();
错误用法2:多个无关的shared_ptr管理同一裸指针
考虑下面这个情况:
int *a = new int;
std::shared_ptr p1(a);
std::shared_ptr p2(a);
p1和p2同时管理同一裸指针a,与之前的例子不同的是,此时的p1和p2有着完全独立的两个引用计数器(初始化p2时,用的是裸指针a,于是我们没有任何办法获取p1的引用计数!),于是,上面的代码会导致a被delete两次,分别由p