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

问题描述 投票:0回答:3

通常,当强引用计数和弱引用计数都为零时,智能指针(强指针或弱指针)将释放控制块。

我很难弄清楚在以下情况下这是如何实现的:线程 A 持有强引用,线程 B 持有弱引用,两者都指向同一个块。

假设强引用正在被销毁,并且强计数已经归零,引用析构函数将调用托管对象的析构函数。到目前为止,一切都很好。

但随后它会检查弱计数并根据该值决定释放块。

在这里,我仍然看到线程 B 中的弱引用和线程 A 中的强引用之间存在竞争的可能性,以释放块两次。弱者可能会自动将其弱计数减少到零,但我不明白为什么强引用在弱者解除分配并再次解除分配之前无法看到这个零

c++ shared-ptr smart-pointers race-condition
3个回答
4
投票

如果我对问题的理解正确,那么您正在想象类似的东西

~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 未完成的

shared_ptr
共享弱计数的一个增量的所有权,在这种情况下,析构函数看起来像:

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

在此设置中,如果有任何未完成的 shared_ptr,则弱计数将为

(# of outstanding weak_ptrs) + 1


1
投票

shared_ptr
的控制块保证线程安全,并且在
shared_ptr
的销毁和任何剩余的
weak_ptr
实例之间不存在竞争。

实际上,这可以通过控制块隐式地将自己视为弱引用来实现。换句话说,弱计数从 1 开始。当对象被销毁时,控制块也会再释放一个弱引用。

比如微软的实现是这样的:

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
投票

弱计数器不用于控制指针对象的分配/释放。它用于控制指针控制块的分配/释放。

也就是说,使用 shared_ptr,你通常会得到这样的结构:

现在我们有 2

shared_ptr
s,所以引用计数为 2。当它达到 0 时,我们销毁
T
和控制块。

但是然后添加几个

weak_ptr
s:

这会产生一些问题。我们想在 ref 计数达到 0 时删除

T
。但是当 refs=0 时我们不能删除控制块,因为那样会使两个
weak_ptr
带有它们“认为”指向控件的指针块,但控制块现在不见了,所以它们有陈旧的指针。

因此,我们添加了第二个引用计数:一个用于

shared_ptr
的数量,一个用于
share_ptr
weak_ptr
的总数。第一个控制删除
T
,第二个控制删除控制块本身。

© www.soinside.com 2019 - 2024. All rights reserved.