Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Boost's intrusive_ptr effectively?

Manual Unref

I have an issue with Boost's intrusive pointer. It's boolean conversion operator checks x.get() != 0. However, the code below fails at the marked point. Why is this the case?

I am guessing that I may have to do with the fact that delete does not set a pointer to 0 (or nullptr). If that's not the case, how could I use the intrusive pointer effectively? I would like to be able to use an intrusive pointer like a regular pointer, e.g., in an expression x && x->foo(), but this artefact seems to preclude it.

#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;
}

(Architecture: Darwin 10.7, tested compilers g++ 4.7 and 4.6 with -std=c++11)

Reference-to-Pointer

After weeding through the source code of intrusive_ptr<T>, I found that there is only one call to intrusive_ptr_release in the destructor:

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

Since the argument px of type T* is an lvalue, it should be possible to set it to zero by slightly changing the function signature of intrusive_ptr_release:

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

Intuitively, this pointer reference-to-pointer parameter should assign the lvalue of p in the calling context to 0. Bjarne also mentions this idiom. However, the assertion still fails at the marked line, leaving me clueless this time.

Example Usage

The reason why I am ref'ing and unref'ing the pointer manually is that I have to work with the raw pointer for a while when passing it to a C API. This means I have to ref it before passing it to the C API in order to prevent destruction, and recreate an intrusive pointer from the raw pointer when I get it back. Here is an example:

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);
}

Here, the second parameter in the construction of y in g() avoids a ref when getting the pointer back from the C API, which compensates for the manual ref in f().

I realized that manually unreffing an intrusive pointer can lead to unexpected behavior, whereas this usage appears to be fine.

like image 922
mavam Avatar asked Mar 25 '12 00:03

mavam


2 Answers

The question is: Why do you expect x to convert to false at the end? You're messing with the ref counter in unexpected ways! You're decreasing it to zero even though there is still an intrusive_ptr — x — that points to the object. That's not how it works. The ref counter is supposed to be at least as great as the number of intrusive_ptr objects that point to the ref counted object — otherwise it would not be a ref counter, would it?

like image 156
sellibitze Avatar answered Nov 07 '22 04:11

sellibitze


Reading the documentation on intrusive_ptr I see there is no connection between "destroying" the object, using its own terminology, and the pointer being 0. So, if you want to use the x && x->foo() idiom, your intrusive_ptr_release function should set the pointer to 0 too.

I can see the design decision here in intrusive_ptr. When intrusive_ptr_release gets called, only destruction should be performed, without including any other behavior than that provided by delete, so if you also want to put the pointer to 0 to support the idiom, you have to do it in your code for that function, but intrusive_ptr itself does not force you to include more restrictions than delete itself: that is, it doesn't force you to reset the pointer to 0.

like image 44
Diego Sevilla Avatar answered Nov 07 '22 05:11

Diego Sevilla