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
)
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.
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.
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?
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With