intrusive pointer, rationale for preferring shared_ptr instead.

http://boost.org/libs/smart_ptr/intrusive_ptr.html says: "As a general rule, if it isn't obvious whether intrusive_ptr better fits your needs than shared_ptr, try a shared_ptr-based design first." I wonder why? Embedding the refcounter into the object seems to me much more natural and performant than adding another free-store allocation which shared_ptr requires. Another related thing is why is there no complementary class to add a refcounter to an object? Something like template<typename ObjectType> struct refcount { refcount(): refs(0) {} ~refcount(){} private: refcount( refcount const&); refcount& operator=(refcount const&); friend void intrusive_ptr_add_ref( ObjectType* o) { static_cast<refcount<ObjectType>*>(o)->add_ref(); } void add_ref() { inc(refs); } friend void intrusive_ptr_release( ObjectType* o) { static_cast<refcount<ObjectType>*>(o)->release(); } void release() { dec(refs); if(!refs) delete static_cast<ObjectType*>(this); } some-integer-type refs; }; (I think that I can't legally inject functions into the surrounding namespace via these friend functions, but maybe there's a workaround and this is just a sketch. ) Uli

On Thursday 06 April 2006 07:50, I wrote:
http://boost.org/libs/smart_ptr/intrusive_ptr.html says: "As a general rule, if it isn't obvious whether intrusive_ptr better fits your needs than shared_ptr, try a shared_ptr-based design first."
I wonder why? Embedding the refcounter into the object seems to me much more natural and performant than adding another free-store allocation which shared_ptr requires.
Nobody wants to even take a guess why? I seriously don't know why one would use shared_ptr by default - I'm aware that some people might want the additional features, but that doesn't justify such an advise.
Another related thing is why is there no complementary class to add a refcounter to an object? Something like [snipped code]
Okay, I tried it now, and propose this here as enhancement for intrinsic pointer. What do you think? Uli #include <iostream> #include <ostream> #include <boost/intrusive_ptr.hpp> namespace boost { template<typename ObjectType> class intrusive_refcount { // not copyable intrusive_refcount( intrusive_refcount const& rhs); // not assignable intrusive_refcount& operator=( intrusive_refcount const& rhs); public:// protected? intrusive_refcount(): refs(0) {} ~intrusive_refcount() { assert(refs==0); } // private with friend? void add_ref() { ++refs; } void release() { if(!--refs) delete static_cast<ObjectType*>(this); } private: size_t refs; }; template<typename T> void intrusive_ptr_add_ref(intrusive_refcount<T>* ptr) { ptr->add_ref(); } template<typename T> void intrusive_ptr_release(intrusive_refcount<T>* ptr) { ptr->release(); } }// end of namespace boost struct foo: boost::intrusive_refcount<foo> { foo() { std::cout << "foo::foo()\n"; } foo( foo const& ) { std::cout << "foo::foo( foo const&)\n"; } foo& operator=( foo const& ) { std::cout << "foo& foo::operator=( foo const&)\n"; return *this; } ~foo() { std::cout << "foo::~foo()\n"; } }; int main() { boost::intrusive_ptr<foo> ptr1( new foo); boost::intrusive_ptr<foo> ptr2; ptr2 = ptr1; }

Ulrich Eckhardt wrote:
On Thursday 06 April 2006 07:50, I wrote:
http://boost.org/libs/smart_ptr/intrusive_ptr.html says: "As a general rule, if it isn't obvious whether intrusive_ptr better fits your needs than shared_ptr, try a shared_ptr-based design first."
I wonder why? Embedding the refcounter into the object seems to me much more natural and performant than adding another free-store allocation which shared_ptr requires.
Nobody wants to even take a guess why? I seriously don't know why one would use shared_ptr by default - I'm aware that some people might want the additional features, but that doesn't justify such an advise.
One reason for the "try shared_ptr first" recommendation is that while intrusive_ptr's advantages are usually obvious to developers, its disadvantages and shared_ptr's advantages are not. They became evident at a later date. intrusive_ptr is obviously the right choice when (1) shared_ptr's overhead is unacceptable, (2) when the design often needs to convert raw pointers into smart pointers, (3) when the underlying API already uses intrusive counting. And, if you pay attention:
"As a general rule, if it isn't obvious whether intrusive_ptr better fits your needs than shared_ptr, try a shared_ptr-based design first."
you'll see that the recommendation is for the cases where it isn't obvious whether intrusive_ptr is a better choice. Since most programmers are naturally biased in favor of intrusive_ptr, this just balances the things out. The main disadvantage of intrusive_ptr is that you need to know about the reference count, which usually means no incomplete classes, and virtual inheritance from a non-abstract class containing data members in every interface. This is manageable, but if you can live without it, you'll certainly want to. Also, weak pointers are cool. Really. :-)

"Peter Dimov" <pdimov@mmltd.net> writes:
And, if you pay attention:
"As a general rule, if it isn't obvious whether intrusive_ptr better fits your needs than shared_ptr, try a shared_ptr-based design first."
you'll see that the recommendation is for the cases where it isn't obvious whether intrusive_ptr is a better choice.
Adding all of the other text your posting to the docs as rationale for the recommendation might be a slick move at this point. -- Dave Abrahams Boost Consulting www.boost-consulting.com

On Monday 10 April 2006 21:59, Peter Dimov wrote:
One reason for the "try shared_ptr first" recommendation is that while intrusive_ptr's advantages are usually obvious to developers, its disadvantages and shared_ptr's advantages are not. They became evident at a later date.
Okay, that's something I understand now. Anyhow, as David suggested, it might be worthwhile to sum up what their (dis)advantages are. For completeness, I'd also include std::auto_ptr. shared_ptr: - Size of the shared_ptr object itself is two pointers. - One additional allocation overhead for shared count, weak count and deleter function pointer (I'm just guessing, is that right?) Further, size is how much? Does it have a specially adapted allocator for those? - Additional freedom with custom deleters. - Allows various type conversions (like raw pointers), including to void or other incomplete types. - Allows weak references (i.e. observers rather than owners) via weak_ptr. - Can be constructed from auto_ptr. - Can be retrieved via 'this', but this requires additional overhead (enable_shared_from_this) in the object itself. intrusive_ptr: - Size of intrusive_ptr object itself is one pointer. - No additional allocation overhead, but space of the refcounter needs to be provided by the object itself (typically one pointer's size). - Can be integrated with existing reference counters (like e.g. COM objects). - Can be converted from/to raw pointers without disadvantages, in particular using 'this'. - Can not be converted like a pointer (upcasting/downcasting) as freely as shared_ptr. std::auto_ptr: - Size of auto_ptr object itself is one pointer (with GCC 4's libstdc++). - No additional overhead since not refcounted. - Can not be instantiated with incomplete types. This works with some implementations as long as at the dtor's instantiation the type is complete but that is not guaranteed by the standard. - Peculiar copying/assignment semantics that assure exclusive ownership but are confusing at first and make it unusable in standard container classes. Uli

UE> shared_ptr: UE> - Size of the shared_ptr object itself is two pointers. UE> - One additional allocation overhead for shared count, weak count and deleter UE> function pointer (I'm just guessing, is that right?) Further, size is how UE> much? Does it have a specially adapted allocator for those? UE> - Additional freedom with custom deleters. UE> - Allows various type conversions (like raw pointers), including to void or UE> other incomplete types. UE> - Allows weak references (i.e. observers rather than owners) via weak_ptr. UE> - Can be constructed from auto_ptr. UE> - Can be retrieved via 'this', but this requires additional overhead UE> (enable_shared_from_this) in the object itself. I would add that shared_ptr can be safely passed between dll's which have different heaps as shared_ptr contains a custom deleter. This is a very important feature. Valentin

On 04/06/2006 12:50 AM, Ulrich Eckhardt wrote:
http://boost.org/libs/smart_ptr/intrusive_ptr.html says: "As a general rule, if it isn't obvious whether intrusive_ptr better fits your needs than shared_ptr, try a shared_ptr-based design first." [snip] Another related thing is why is there no complementary class to add a refcounter to an object? Something like
template<typename ObjectType> struct refcount [snip] overhead_referent_vals here:
http://tinyurl.com/k9jhr does this, but with multiple inheritance instead of single. It's designed to be used with auto_overhead ( located in the same directory ), which assures the pointer used in a smart_ptr points into the heap. A closely related discussion is here: http://archives.free.net.ph/message/20060331.100537.108c5fab.en.html However the OP of that thread was just concerned ( AFAICT ) with the thread safety of creating the pointee, but auto_overhead is only concerned with assuring that the argument to the smart_ptr CTOR must be (as enforced by the compiler) from the heap. I have plans to eventually add a source/sink policy to the policy_ptr in the sandbox as proposed in may2005 (but didn't generate much interest). The sketchy proposal is here: http://archives.free.net.ph/message/20050511.215426.101ccded.en.html
participants (5)
-
David Abrahams
-
Larry Evans
-
Peter Dimov
-
Ulrich Eckhardt
-
Valentin Samko