如何有效地使用Boost的intrusive_ptr?

时间:2012-03-25 00:22:11

标签: c++ c++11 memory-management boost smart-pointers

手动取消参考

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

我猜我可能与delete does not set a pointer to 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,使用-std=c++11测试编译器g ++ 4.7和4.6)

参考到指针

在浏览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 mentions this idiom。然而,断言仍然在标记线处失败,这次让我一无所知。

使用示例

我手动修改和解释指针的原因是我必须在将原始指针传递给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);
}

此处,yg()构造中的第二个参数在从C API返回指针时避免了引用,这补偿了f()中的手动引用。 / p>

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

2 个答案:

答案 0 :(得分:13)

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

答案 1 :(得分:2)

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

我可以在intrusive_ptr看到设计决策。调用intrusive_ptr_release时,只应执行销毁,不包括delete提供的任何其他行为,因此如果您还想将指针放到0以支持该习惯用法,您必须在该函数的代码中执行此操作,但intrusive_ptr本身不会强制您包含比delete本身更多的限制:也就是说,它不会强制您将指针重置为{ {1}}。