在gnu c++中讨论shared_ptr和weak_ptr只需要讨论__shared_count 和__weak_count ,另外就是weak_ptr 没有定义operator -> 和operator * ,所以没有想原始指针那样的行为。
首先由简入繁。从简单的weak_ptr开始看起,然后研究shared_ptr
类图
__weak_count
class __weak_count {
...
private:
friend class __shared_count<_Lp>;
_Sp_counted_base<_Lp>* _M_pi;
};
__weak_count 只有一个成员变量,这是一个多态指针。
void _M_swap(__weak_count& __r) noexcept {
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
__r._M_pi = _M_pi;
_M_pi = __tmp;
}
可以看到两个weak_prt 的交换,实际上交换了相互的_M_pi 这个多态指针
weak_ptr 没有接受原始指针的构造,是能通过weak_ptr 和shared_ptr 产生。
1. weak_ptr ->weak_ptr :
weak_ptr 对weak_ptr :拷贝构造和拷贝赋值
__weak_count(const __weak_count& __r) noexcept : _M_pi(__r._M_pi) {
if (_M_pi != nullptr)
_M_pi->_M_weak_add_ref();
}
__weak_count& operator=(const __weak_count& __r) noexcept {
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
if (__tmp != nullptr)
__tmp->_M_weak_add_ref();
if (_M_pi != nullptr)
_M_pi->_M_weak_release();
_M_pi = __tmp;
return *this;
}
拷贝构造:入参的多态指针不为空,则共享的弱引用计数+1
拷贝赋值:入参的多态指针不为空,共享弱引用计数+1
? 原多态指针不为空就释放,然后交换相互的_M_pi 这个多态指针
2. shared_ptr ->weak_ptr :
__weak_count(const __shared_count<_Lp>& __r) noexcept : _M_pi(__r._M_pi) {
if (_M_pi != nullptr)
_M_pi->_M_weak_add_ref();
}
__weak_count& operator=(const __shared_count<_Lp>& __r) noexcept {
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
if (__tmp != nullptr)
__tmp->_M_weak_add_ref();
if (_M_pi != nullptr)
_M_pi->_M_weak_release();
_M_pi = __tmp;
return *this;
}
拷贝构造:入参的多态指针不为空,则共享的弱引用计数+1
拷贝赋值:入参的多态指针不为空,共享弱引用计数+1
? 原多态指针不为空就释放,然后交换相互的_M_pi 这个多态指针
long _M_get_use_count() const noexcept {
return _M_pi != nullptr ? _M_pi->_M_get_use_count() : 0;
}
weak_prt 的计数,其实是调用多态指针_M_pi 的虚函数
但是!
但是!
但是!,上面提到的多态指针_Sp_counted_base<_Lp>* __weak_count::_M_pi; ,并没有new 的地方,注定它的来源是shared_ptr ,上述代码中的对其弱引用计数的修改,实际上是修改的shared_ptr 的弱引用计数;对引用计数的获取,实际上是对shared_ptr 的引用计数的获取。所以weak_ptr 是的shared_ptr 附属品。
void _M_swap(__shared_count& __r) noexcept {
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
__r._M_pi = _M_pi;
_M_pi = __tmp;
}
可以看到两个__shared_count 的交换,实际上交换了相互的_M_pi 这个多态指针,和上面__weak_count 的交换一样,因为本就指向同一块内存,同一个(堆)对象。
__shared_count
1. raw ptr ->shared_ptr
由一颗原始指针去构造shared_ptr 是一切的开始:
__shared_count(_Ptr __p) : _M_pi(0) {
__try {
_M_pi = new _Sp_counted_ptr<_Ptr, _Lp>(__p);
}
__catch(...) {
delete __p;
__throw_exception_again;
}
}
将原始指针用于堆对象_Sp_counted_ptr 的构造,多态指针_M_pi 将其引用,_Sp_counted_ptr 从此开始维护引用计数和RAII
2. shared_ptr ->shared_ptr
__shared_count(const __shared_count& __r) noexcept : _M_pi(__r._M_pi) {
if (_M_pi != 0)
_M_pi->_M_add_ref_copy();
}
__shared_count& operator=(const __shared_count& __r) noexcept {
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
if (__tmp != _M_pi) {
if (__tmp != 0)
__tmp->_M_add_ref_copy();
if (_M_pi != 0)
_M_pi->_M_release();
_M_pi = __tmp;
}
return *this;
}
拷贝构造:入参的多态指针不为空,则共享的引用计数+1
拷贝赋值:入参的多态指针不为空,共享弱引用计数+1
? 原多态指针不为空就释放,然后交换相互的_M_pi 这个多态指针
3. weak_ptr ->shared_ptr
__shared_count(const __weak_count& __r)
: _M_pi(__r._M_pi) {
if (_M_pi != nullptr)
_M_pi->_M_add_ref_lock();
else
__throw_bad_weak_ptr();
}
如果弱引用计数中的多态指针_M_pi 不为空,则引用计数+1
如果弱引用计数中的多态指针_M_pi 为空,则抛出异常,即未引用weak_ptr 如果不是生于某shared_ptr ,将不能用作新的shared_ptr 的创建,而什么时候使用weak_ptr 去创建新的shared_ptr 呢,怎么创建呢,一会儿分析。
long _M_get_use_count() const noexcept {
return _M_pi != 0 ? _M_pi->_M_get_use_count() : 0;
}
bool _M_unique() const noexcept { return this->_M_get_use_count() == 1; }
_Sp_counted_base
_Sp_counted_base() noexcept : _M_use_count(1), _M_weak_count(1) {}
virtual ~_Sp_counted_base() noexcept {}
virtual void _M_dispose() noexcept = 0;
virtual void _M_destroy() noexcept { delete this; }
可以看到抽象类_Sp_counted_base 的构造函数,初始化了int类型的成员变量:_M_use_count ,_M_weak_count 为1,即在原始指针构造shared_ptr 时在堆上开辟内存,进行了初始化。
_M_destroy 函数,用于销毁自己(实现类**_Sp_counted_ptr ),而管理的资源的销毁放在了子类实现(_Sp_counted_ptr **)之中,可以推断,当功能项的子类堆对象析构时,或者析构前,会对管理的资源进行回收。
纯虚接口virtual void _M_dispose() noexcept = 0; 在子类中的重写如下:
class _Sp_counted_ptr final : public _Sp_counted_base {
public:
explicit _Sp_counted_ptr(_Ptr __p) noexcept : _M_ptr(__p) {}
virtual void _M_dispose() noexcept { delete _M_ptr; }
virtual void _M_destroy() noexcept { delete this; }
_Sp_counted_ptr(const _Sp_counted_ptr&) = delete;
_Sp_counted_ptr& operator=(const _Sp_counted_ptr&) = delete;
private:
_Ptr _M_ptr;
};
可以看到实现了_M_dispose() ,其将资源真正释放。
而且,重写了_M_destroy() ,将自身析构。
接下来就看引用计数加减逻辑和资源释放逻辑。
由于存在多线曾多共享引用计数进行访问,因此对于_M_use_count ,_M_weak_count 都要进行原子操作:
void _M_add_ref_copy() { __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }
void _M_add_ref_lock();
bool _M_add_ref_lock_nothrow();
void _M_release() noexcept {
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1) {
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
_M_dispose();
if (_Mutex_base<_Lp>::_S_need_barriers) {
__atomic_thread_fence(__ATOMIC_ACQ_REL);
}
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1) {
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
_M_destroy();
}
}
}
void _M_weak_add_ref() noexcept {
__gnu_cxx::__atomic_add_dispatch(&_M_weak_count, 1);
}
void _M_weak_release() noexcept {
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1) {
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
if (_Mutex_base<_Lp>::_S_need_barriers) {
__atomic_thread_fence(__ATOMIC_ACQ_REL);
}
_M_destroy();
}
}
long _M_get_use_count() const noexcept {
return __atomic_load_n(&_M_use_count, __ATOMIC_RELAXED);
}
void _M_add_ref_copy() 将引用计数+1
void _M_weak_add_ref() 将弱引用计数+1
void _M_weak_release() ,首先判断引用计数-1之前,即当前引用计数值是否为1,为1就代表没有别的shared_ptr 在引用该资源,调用使用实现类重写的_M_destroy() 将自己析构,做到了共享内存不泄露。
void _M_release() ,首先判断引用计数-1之前,即当前引用计数值是否为1,为1就代表没有别的shared_ptr 在引用该资源,于是调用使用实现类重写的_M_dispose() 将资源释放!然后调用使用实现类重写的_M_destroy() 将自己析构,做到了共享内存不泄露。
那么什么时候调用_M_weak_release() ,什么时候调用_M_release() 呢?并且为什么调用_M_weak_release() 要去回收资源呢?
1. 调用_M_weak_release()
在weak_ptr 对weak_ptr 进行赋值时,左值将原本的弱引用计数-1时会调用。
在某个weak_ptr 析构时,引用计数-1时会调用。
那么,是否存在共享内存中弱引用计数从1减到0的情况呢?答案是不存在:
1. 当环境中不存在`weak_ptr`时,弱引用技术保持1。
2. 当环境中每次从`shared_ptr`中诞生一个`weak_ptr`时,弱引用技术+1,所以最终会递减到1,也不会存在0的情况。
因此weak_ptr 不会引起管理的资源的回收。
2. 调用_M_release()
在shared_ptr 对shared_ptr 进行赋值时,左值将原本的弱引用计数-1时会调用。
在某个shared_ptr 析构时,引用计数-1时会调用。
由于weak_ptr 是由shared_ptr 生出,并且也能使用weak_ptr 去构造shared_ptr ,那么势必能将weak_ptr 提升为shared_ptr :
shared_ptr<_Tp> lock() const noexcept {
return shared_ptr<_Tp>(*this, std::nothrow);
}
这样做会增加引用计数,但是不增加弱引用技术,这样做的好处在于,当想要使用weak_ptr 管理的资源时担心资源是否被释放,然后哦按段shared_ptr ,如果为真,则资源未释放。
enable_shared_from_this
template <typename _Tp, _Lock_policy _Lp>
class __enable_shared_from_this {
protected:
constexpr __enable_shared_from_this() noexcept {}
__enable_shared_from_this(const __enable_shared_from_this&) noexcept {}
__enable_shared_from_this& operator=(
const __enable_shared_from_this&) noexcept {
return *this;
}
~__enable_shared_from_this() {}
public:
__shared_ptr<_Tp, _Lp> shared_from_this() {
return __shared_ptr<_Tp, _Lp>(this->_M_weak_this);
}
__shared_ptr<const _Tp, _Lp> shared_from_this() const {
return __shared_ptr<const _Tp, _Lp>(this->_M_weak_this);
}
__weak_ptr<_Tp, _Lp> weak_from_this() noexcept { return this->_M_weak_this; }
__weak_ptr<const _Tp, _Lp> weak_from_this() const noexcept {
return this->_M_weak_this;
}
private:
template <typename _Tp1>
void _M_weak_assign(_Tp1* __p,
const __shared_count<_Lp>& __n) const noexcept {
_M_weak_this._M_assign(__p, __n);
}
friend const __enable_shared_from_this* __enable_shared_from_this_base(
const __shared_count<_Lp>&, const __enable_shared_from_this* __p) {
return __p;
}
template <typename, _Lock_policy>
friend class __shared_ptr;
mutable __weak_ptr<_Tp, _Lp> _M_weak_this;
};
可以看到这个类,含有一个数据成员:_M_weak_this : __weak_ptr<_Tp>
__shared_ptr<_Tp, _Lp> shared_from_this() { return __shared_ptr<_Tp, _Lp>(this->_M_weak_this); }
__weak_ptr<_Tp, _Lp> weak_from_this() noexcept { return this->_M_weak_this; }
其中shared_from_this() 由内部weak_ptr 产生一个shared_ptr ,使得共享shared_ptr 引用计数+1,那么其内部是如何被初始化的呢?从上面看来是通过调用:
template <typename _Tp1>
void _M_weak_assign(_Tp1* __p,
const __shared_count<_Lp>& __n) const noexcept {
_M_weak_this._M_assign(__p, __n);
}
再看一下构造shared_ptr时,入参为enable_shared_frome_this 的构造函数:
template <typename _Yp, typename = _SafeConv<_Yp>>
explicit __shared_ptr(_Yp* __p)
: _M_ptr(__p), _M_refcount(__p, typename is_array<_Tp>::type()) {
static_assert(!is_void<_Yp>::value, "incomplete type");
static_assert(sizeof(_Yp) > 0, "incomplete type");
_M_enable_shared_from_this_with(__p);
}
template <typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
typename enable_if<__has_esft_base<_Yp2>::value>::type
_M_enable_shared_from_this_with(_Yp* __p) noexcept {
if (auto __base = __enable_shared_from_this_base(_M_refcount, __p))
__base->_M_weak_assign(const_cast<_Yp2*>(__p), _M_refcount);
}
friend const __enable_shared_from_this* __enable_shared_from_this_base(
const __shared_count<_Lp>&, const __enable_shared_from_this* __p) {
return __p;
}
在初次使用继承了enable_shared_from_this 的堆对象取构造shared_ptr 时,其内部回调用_M_enable_shared_from_this_with(__p) 去接收这个原始指针。 然后在_M_enable_shared_from_this_with(__p) 中通过友元函数__enable_shared_from_this_base 返回一个enable_shared_from_this 的指针,也就是将实际类型指针转型为父类指针。最后调用_M_weak_assign 初始化其内weak_ptr 。 此时共享的控制块中引用计数为1,弱引用计数也是1+1=2
所以当用户敲下以下代码的时候,就表示当前资源已经被一个weak_ptr 引用了
可以推测,一个被shared_ptr管理的资源,通过std::enable_shared_from_this::shared_frome_this() 来产生一个共享同一个资源控制快的新shared_ptr ,从而保证在第一个shared_ptr 声明结束的时候,由于还有shared_ptr 管理,因此生命周期被延长了。比如:
struct B {
auto asyncFunc(std::function<void()> callback) {
std::async([=] {
sleep(100);
callback();
});
}
};
struct A : std::enable_shared_from_this<A> {
B b_;
auto callBack() {}
auto func() { b_.asyncFunc(std::bind(&A::callBack, shared_frome_this())); }
};
{
auto a = make_shared<A>();
a->func();
}
总结weak_ptr的作用
- 解决循环引用
- 实现弱回调,不延长对象生命周期,只作为对shared_ptr的观测。回调前判断提升结果,提升成功才做回调。
|