11

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.

Community
  • 1
  • 1
mavam
  • 12,242
  • 10
  • 53
  • 87

2 Answers2

13

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?

sellibitze
  • 27,611
  • 3
  • 75
  • 95
  • 3
    Put in other words: Intrusive pointers in general, and `intrusive_ptr` in particular (and also Microsoft COM pointers), work correctly when all of its users obey **ownership semantics with its pointers**, whether these pointers are C++ templates or raw C pointer. **Ownership semantics** requires that any **refcount-changing event only occur via a pointer (raw or smart) under the condition that ownership by that pointer is ascertained**. (In layman terms, you don't meddle what you don't own.) In OP's code sample, the `(intrusive_ptr) x` is the one which has certain ownership of the object. – rwong Apr 19 '14 at 07:14
2

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.

Diego Sevilla
  • 28,636
  • 4
  • 59
  • 87
  • I understand the functions `intrusive_ptr_add_ref` and `intrusive_ptr_release` to take a raw pointer rather than an `intrusive_ptr`. This would not allow for setting the private `intrusive_ptr` member `px` to `0`, or do you mean something else? – mavam Mar 25 '12 at 02:33
  • Ah, I see what you mean. The pointer you get in the function is copied by value, so you cannot modify it. I guess then that this technique can be used as a one-time wrapper of directly received values, to perform (locally, inside a function) a reference counted work with the pointer. – Diego Sevilla Mar 25 '12 at 19:35
  • Would you mind explaining your last comment? I'm not sure if I understand correctly. Perhaps I addressed what you mean with my question edit. – mavam Mar 25 '12 at 20:15
  • Yes, I was about to suggest the change to a `T*&`, but I'm not sure if this is allowed by the contract of `intrusive_ptr`. Doing that function in your edit you get the behavior you expect, so I guess it is "practically right". – Diego Sevilla Mar 25 '12 at 20:29