c++ 在这种情况下,`shared_ptr`和`weak_ptr`如何避免泄漏?

wwodge7n  于 2023-04-01  发布在  其他
关注(0)|答案(3)|浏览(115)

通常,当强和弱引用计数都达到0时,智能指针(强或弱)将释放控制块。
在下面的场景中,我有一个问题要弄清楚这是如何实现的:线程A持有强引用,线程B持有弱引用,两者都指向同一个块。
假设强引用正在被销毁,并且强引用计数已达到0,引用析构函数将调用托管对象的析构函数。到目前为止,一切顺利。
但随后它会检查weakrefcount并根据该值决定解除分配块。
这里我仍然看到线程B中的弱引用和线程A中的强引用之间可能会有两次释放块的竞争。弱引用可能会将其弱引用计数原子地减少到0,但我不明白为什么强引用在弱引用释放之前不能看到这个0,并再次释放。

qq24tv8q

qq24tv8q1#

如果我没理解错的话,你是在想象

~shared_ptr() {
   if (--strong_ref == 0) {
       destroy_object();
       if (weak_ref == 0) {
           deallocate_block();
       }
   }
}
~weak_ptr() {
    if (--weak_ref == 0) {
        deallocate_block();
    }
}

这确实会导致deallocate_block()被调用两次,因此,这不是一个正确的实现。
诀窍是使shared_ptr构造增量(和破坏减量)同时为强引用计数和弱引用计数:

~shared_ptr() {
   if (--strong_ref == 0) {
       destroy_object();
   }
   if (--weak_ref == 0) {
       deallocate_block();
   }
}

有一个shared_ptr和一个weak_ptr未完成,弱计数将是2,而不是1。当两者都被销毁时,deallocate_block将只被调用一次-由将弱计数递减到零的析构函数调用。
作为一种优化,我们可以让给定控制块的 all outstanding shared_ptr s共享弱计数的一个增量的所有权,在这种情况下析构函数看起来像这样:

~shared_ptr() {
   if (--strong_ref == 0) {
       destroy_object();
       if (--weak_ref == 0) {
           deallocate_block();
       }
   }
}

在此设置中,如果有任何shared_ptr未完成,则弱计数将为(# of outstanding weak_ptrs) + 1

u4dcyp6a

u4dcyp6a2#

shared_ptr的控制块保证是线程安全的,并且在shared_ptr的销毁和任何剩余的weak_ptr示例之间不存在竞争。
实际上,这可以通过控制块隐式地将自身视为弱引用来实现。换句话说,弱计数从1开始。当对象被销毁时,控制块也会释放一个弱引用。
例如,Microsoft的实现是这样的:

class __declspec(novtable) _Ref_count_base { // common code for reference counting

    // ...

private:
    _Atomic_counter_t _Uses  = 1;
    _Atomic_counter_t _Weaks = 1;

public:
    void _Decref() noexcept { // decrement use count
        if (_MT_DECR(_Uses) == 0) {
            _Destroy();
            _Decwref();
        }
    }

    void _Decwref() noexcept { // decrement weak reference count
        if (_MT_DECR(_Weaks) == 0) {
            _Delete_this();
        }
    }

    // ...

};

上面,当_Uses达到0时,对象被销毁,然后释放一个弱引用。如果这导致没有剩余的弱引用,则控制块被销毁。

mzillmmw

mzillmmw3#

弱计数器不是用来控制指针对象的分配/释放,而是用来控制指针控制块的分配/释放。
也就是说,使用shared_ptr时,通常会得到如下结构:

现在我们有2个shared_ptr s,所以ref计数为2。当它达到0时,我们销毁T和控制块。
然后再加上几个weak_ptr

这就产生了一点问题。我们想在ref计数达到0时删除T。但是当refs=0时我们不能删除控制块,因为这会给两个weak_ptr留下它们“认为”指向一个控制块的指针,但是控制块现在已经消失了,所以它们有过时的指针。
因此,我们添加第二个ref count:一个用于shared_ptr s的数量,一个用于share_ptr s和weak_ptr s的总数。第一个控制T的删除,第二个控制控制块本身的删除。

相关问题