问题 如何有效地使用Boost的intrusive_ptr?


手动Unref

我对Boost的侵入式指针有一个问题。它的 布尔转换运算符 检查 x.get() != 0。但是,下面的代码在标记点处失败。为什么会这样?

我猜我可能与这个事实有关 delete 没有设置指针 0 (要么 nullptr)。如果不是这样,我怎么能有效地使用侵入式指针?我希望能够使用像常规指针这样的侵入式指针,例如,在表达式中 x && x->foo(),但这个人工制品似乎排除了它。

#include <atomic>
#include <boost/intrusive_ptr.hpp>

struct T
{
    T() : count(0u) { }

    size_t ref_count()
    {
        return count;
    }

    std::atomic_size_t count;
};

void intrusive_ptr_add_ref(T* p)
{
    ++p->count;
}

void intrusive_ptr_release(T* p)
{
    if (--p->count == 0u)
        delete p;
}

int main()
{
    boost::intrusive_ptr<T> x;
    x = new T;
    assert(x->ref_count() == 1);

    auto raw = x.get();
    intrusive_ptr_add_ref(raw);
    intrusive_ptr_add_ref(raw);
    assert(x->ref_count() == 3);

    intrusive_ptr_release(raw);
    intrusive_ptr_release(raw);
    assert(x->ref_count() == 1);

    intrusive_ptr_release(raw); // Destroys T, ref_count() == 0.
    assert(! x); // Fails.

    return 0;
}

(架构:Darwin 10.7,测试编译器g ++ 4.7和4.6 with -std=c++11

参考到指针

通过源代码除草后 intrusive_ptr<T>,我发现只有一个电话 intrusive_ptr_release 在析构函数中:

~intrusive_ptr()
{
    if( px != 0 ) intrusive_ptr_release( px );
}

自从争论 px 类型 T* 是一个左值,应该可以通过稍微改变函数签名将其设置为零 intrusive_ptr_release

inline void intrusive_ptr_release(T*& p)
{
    if (--p->count == 0u)
    {
        delete p;
        p = 0;
    }
}

直观地说,这个指针引用指针参数应该赋值的左值 p 在调用语境中为0. Bjarne也 提到这个成语。然而,断言仍然在标记线处失败,这次让我无能为力。

示例用法

我手动修改和取消指针的原因是我必须在将原始指针传递给C API时使用一段时间。这意味着我必须在将它传递给C API之前对其进行引用以防止破坏,并在我将其返回时从原始指针重新创建一个侵入式指针。这是一个例子:

void f()
{
    intrusive_ptr<T> x = new T;
    auto raw = x.get();
    intrusive_ptr_add_ref(raw);
    api_in(raw);
}

void g()
{
    T* raw = api_out();
    intrusive_ptr<T> y(raw, false);
    h(y);
}

这里,第二个参数在构造中 y 在 g() 从C API返回指针时避免使用ref,这会补偿手动引用 f()

我意识到手动不提供侵入式指针会导致意外行为,而这种用法看起来很好。


12649
2018-03-25 00:22


起源



答案:


问题是:为什么期望x最终转换为false?你以意想不到的方式搞乱了参考柜台!你把它减少到零 即使 还有一个 intrusive_ptr  - x - 指向对象。这不是它的工作原理。 ref计数器应该至少与数量一样大 intrusive_ptr 指向ref计数对象的对象 - 否则它不会是ref计数器,是吗?


13
2018-03-26 07:59



换句话说:一般的侵入式指针,和 intrusive_ptr 特别是(以及Microsoft COM指针),当所有用户服从时,它可以正常工作 所有权语义及其指针,无论这些指针是C ++模板还是原始C指针。 所有权语义 要求任何 refcount-changing事件仅在确定该指针的所有权的条件下通过指针(原始或智能)发生。 (通俗地说,你不会插入你不拥有的东西。)在OP的代码示例中, (intrusive_ptr) x 是具有对象的某种所有权的那个。 - rwong


答案:


问题是:为什么期望x最终转换为false?你以意想不到的方式搞乱了参考柜台!你把它减少到零 即使 还有一个 intrusive_ptr  - x - 指向对象。这不是它的工作原理。 ref计数器应该至少与数量一样大 intrusive_ptr 指向ref计数对象的对象 - 否则它不会是ref计数器,是吗?


13
2018-03-26 07:59



换句话说:一般的侵入式指针,和 intrusive_ptr 特别是(以及Microsoft COM指针),当所有用户服从时,它可以正常工作 所有权语义及其指针,无论这些指针是C ++模板还是原始C指针。 所有权语义 要求任何 refcount-changing事件仅在确定该指针的所有权的条件下通过指针(原始或智能)发生。 (通俗地说,你不会插入你不拥有的东西。)在OP的代码示例中, (intrusive_ptr) x 是具有对象的某种所有权的那个。 - rwong


阅读文档 intrusive_ptr 我看到“销毁”对象,使用自己的术语,指针为0之间没有联系。所以,如果你想使用 x && x->foo() 成语,你的 intrusive_ptr_release 函数应该将指针设置为 0 太。

我可以在这里看到设计决定 intrusive_ptr。什么时候 intrusive_ptr_release 被调用,只应执行销毁,不包括任何其他行为 delete,所以如果你还想把指针放到 0 为了支持这个成语,你必须在你的代码中为这个功能做,但是 intrusive_ptr本身不会强迫你包含更多的限制 delete 本身:也就是说,它不会强制您将指针重置为 0


2
2018-03-25 00:42



我理解这些功能 intrusive_ptr_add_ref 和 intrusive_ptr_release 采取原始指针而不是 intrusive_ptr。这不允许设置私有 intrusive_ptr 会员 px 至 0或者你的意思是别的什么? - mavam
啊,我明白你的意思了。您在函数中获得的指针将按值复制,因此您无法对其进行修改。我想那个技术可以用作直接接收值的一次性包装器,用指针执行(本地,在函数内部)引用计数工作。 - Diego Sevilla
你介意解释你的最后评论吗?我不确定我是否理解正确。也许我用我的问题编辑解决了你的意思。 - mavam
是的,我打算建议改变一个 T*&,但我不确定合同是否允许这样做 intrusive_ptr。在编辑中执行该功能可以获得您期望的行为,因此我猜它“实际上是正确的”。 - Diego Sevilla