weak_ptr for intrusive_ptr

I greatly prefer intrusive_ptr to shared_ptr, but one drawback of that is there is no equivalent of weak_ptr. I have started work on a class to do that and my biggest question, honestly, was what to name it. The basic logic turned out to be fairly simple. The target class declares a member that is an instance of the weak pointer. Other classes that want a weak pointer to the target instance also declare a member weak pointer, which must be initialized with a copy of the target instance member. The member provides a method to generate an intrusive_ptr to the target, which is either valid or nil depending on whether the target has been destructed. When the target and all weak pointers are destructed, everything gets cleaned up. The weak pointers don't affect the target reference count. No changes to Boost.intrusive_ptr are required. The one improvement I see would be to merge it into the reference counter utility class discussed earlier (optionally, of course - perhaps via a subclass of the reference counter). Yeah, I think I'll do that. My questions: 1) What would be a good name for such a class? I am using "tell_tale" for now, in analogy to tell tale lights on devices that go out when the device fails. Obviously "weak_ptr" is out. "weak_intrusive_ptr" seems a bit verbose, but may that's OK. 2) Is there any interest in Boostifying the class, or am I the only one who would use it? It's a matter of how much I indulge my idiosyncrasies.

On Sun, Aug 10, 2008 at 9:47 AM, Alan M. Carroll <amc@network-geographics.com> wrote:
I greatly prefer intrusive_ptr to shared_ptr, but one drawback of that is there is no equivalent of weak_ptr. I have started work on a class to do that and my biggest question, honestly, was what to name it.
Have you considered the allocator support in shared_ptr? It makes it possible to allocate the control block in the same memory block as the managed object. Between this and the aliasing support in shared_ptr, I've found intrusive_ptr's use rather limited. Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode

Alan M. Carroll:
I greatly prefer intrusive_ptr to shared_ptr, but one drawback of that is there is no equivalent of weak_ptr. I have started work on a class to do that and my biggest question, honestly, was what to name it.
The basic logic turned out to be fairly simple. The target class declares a member that is an instance of the weak pointer. Other classes that want a weak pointer to the target instance also declare a member weak pointer, which must be initialized with a copy of the target instance member. The member provides a method to generate an intrusive_ptr to the target, which is either valid or nil depending on whether the target has been destructed. When the target and all weak pointers are destructed, everything gets cleaned up. The weak pointers don't affect the target reference count. No changes to Boost.intrusive_ptr are required.
Will you make the code available for review?

At 09:18 AM 8/14/2008, you wrote:
Alan M. Carroll:
I greatly prefer intrusive_ptr to shared_ptr, but one drawback of that is there is no equivalent of weak_ptr. I have started work on a class to do that and my biggest question, honestly, was what to name it.
Will you make the code available for review?
Yes. I have a lot of testing and Boostifcation to do before then.

Alan M. Carroll:
At 09:18 AM 8/14/2008, you wrote:
Alan M. Carroll:
I greatly prefer intrusive_ptr to shared_ptr, but one drawback of that is there is no equivalent of weak_ptr. I have started work on a class to do that and my biggest question, honestly, was what to name it.
Will you make the code available for review?
Yes. I have a lot of testing and Boostifcation to do before then.
I didn't mean a formal Boost review... just a "can we take a look at it" review.

At 08:33 AM 8/20/2008, you wrote:
I didn't mean a formal Boost review... just a "can we take a look at it" review.
Allright. I did a bit of cleanup and testing this morning and this is what I have. Known issues: * May not handle cross assignment of target object. * No thread safety (not something I need, but should be thought about during Boostification) # pragma once // Copyright 2008 Network Geographics Inc. // All rights reserved. # include <boost/intrusive_ptr.hpp> /** @file Extensions for Boost.instrusive_ptr. */ namespace boost { /** Helper template mixin for @c boost::intrusive_ptr. To add support for @c boost::intrusive_ptr to class @c T, it should inherit from @c reference_counter<T>. This will - provide a reference count member - force the reference count to initialize to zero - define the add and release global functions required by @c intrusive_ptr If this class is not inherited publically, then the class @c T must declare the global functions as friends to permit them to cast @c T* to @c reference_counter<T>*. This can be done with the following two lines, with either the @c self typedef or the string "self" replaced by the actual name of class @c T. @code friend void boost::intrusive_ptr_add_ref(self const*); friend void boost::intrusive_ptr_release(self const*); @endcode @note Due to changes in the C++ standard and design decisions in gcc, it is no longer possible to declare a template parameter as a friend class. (Basically, you can't use a typedef in a friend declaration and gcc treats template parameters as typedefs). @note You can use this with insulated (by name only) classes. The important thing is to make sure that any class that uses an @c intrusive_ptr to a by name only class has all of its constructors and destructors declared in the header and defined in the implementation translation unit. If the compiler generates any of those, it will not compile due to missing functions or methods. */ template <typename T> class reference_counter { protected: typedef reference_counter self; //!< Useful for self type reference //! Default constructor. reference_counter() : _reference_count(0) { } /** Copy constructor. @note Always initialize to zero. Ignore the source count. This makes it nicer for clients who can now use the default copy constructor. */ reference_counter(self const&) : _reference_count(0) { } /** Assignment operator. @note Prevent the count from being assigned. */ self& operator= (self const&) { return *this; } /// Check for multiple references. /// @return @c true if more than one smart pointer references the object, @c false otherwise. bool is_shared() const { return _reference_count > 1; } /// Check for a single reference (@c shared_ptr compatibility) bool unique() const { return _reference_count <= 1; } /// Number of references (@c shared_ptr compatibility) long use_count() const { return _reference_count; } mutable long _reference_count; //!< The reference count //! Define the helper functions and give them access //!@{ friend inline void intrusive_ptr_add_ref(T const* t) { ++(static_cast<self const*>(t)->_reference_count); } friend inline void intrusive_ptr_release(T const* t) { if (--(static_cast<self const*>(t)->_reference_count) <= 0) delete t; } //!@} }; // forward declare template < typename T > class intrusive_monitor; /** Weak pointer for @c intrusive_ptr. This object contains a reference to a target object. An @c intrusive_ptr to that target object can be obtained from this object. If the target object has been destructed, the @c intrusive_ptr will be @c nil. @note The target object class must provide support for this to work. @see intrusive_monitor */ template < typename T > class weak_intrusive_ptr { protected: /// The structure allocated to watch the target object. /// It exists only to hold a raw pointer to the target. /// The pointer is reset to @c nil when the target is destructed. struct monitor : public reference_counter<monitor> { typedef monitor self; ///< Self reference type typedef intrusive_ptr<self> handle; ///< Smart pointer type /// Construct with target monitor(T* target) : _target(target) {} T* _target; ///< The target object. }; typename monitor::handle _monitor; ///< Our reference to the monitor friend class intrusive_monitor<T>; private: typedef weak_intrusive_ptr self; ///< Self reference type public: /** Default constructor. A default constructed @c intrusive_weak_ptr acts as if the target object has been destructed. Use assignment to re-target. */ weak_intrusive_ptr() {} /// Construct from raw pointer weak_intrusive_ptr(T* target) { *this = target; } /// Construct from intrusive pointer weak_intrusive_ptr(intrusive_ptr<T> const& ptr) { *this = ptr.get(); } /// Assign intrusive pointer self& operator = (intrusive_ptr<T> const& ptr) { return *this = ptr.get(); } /// Assign raw pointer self& operator = (T* target) { intrusive_monitor<T>* im = static_cast< intrusive_monitor<T>* >(target); if (im) { // Subtle - if the monitor hasn't been initialized at all, create it. // But! if it's there, do *not* set the target because the monitor is // always initialized with a valid pointer. The monitor being present // with a @c nil target means it's a dangling pointer and we need to stop digging. if (!im->_intrusive_monitor) im->_intrusive_monitor = new monitor(target); _monitor = im->_intrusive_monitor; } return *this; } /** Get an @c intrusive_ptr to the target. @return A valid smart pointer to the target object if it has not been destructed, a @a nil smart pointer otherwise. */ intrusive_ptr<T> lock() const { return intrusive_ptr<T>(_monitor ? _monitor->_target : 0); } }; /** Helper template mixin for @c boost::weak_intrusive_ptr. To add support for @c weak_intrusive_ptr to class @c T, it should inherit from @c intrusive_monitor<T>. This will - provide a reference count member - force the reference count to initialize to zero - define the add and release global functions required by @c intrusive_ptr - create a monitor to support intrusive_weak_ptr This class inherits from @c reference_counter and all requirements for that are also requirements for this mixin. This adds the monitor for weak pointers to the target object. It is not necessary to explicitly initialize the monitor. If it is in the default constructed state when a @c weak_intrusive_ptr is created, it will initialized appropriately. In that case, the allocation cost of monitoring isn't paid unless used. Explicit intialization of the monitor, if desired, can be done either by use of base class initialization or by use of the inherited @c init_intrusive_monitor method. Which is appropriate depends on whether the target class uses member initialization or has its own @c init method called from its various constructors. @code class A : public boost::intrusive_monitor<A> { // This will generate warning C4355 in Microsoft Visual Studio, which can be ignored. A() : intrusive_monitor(this) { ... } }; @endcode or @code class A : public boost::intrusive_monitor<A> { A() { ... this->init(); ... } void init() { // ... this->init_intrusive_monitor(this); // ... } }; @endcode */ template < typename T > class intrusive_monitor : public reference_counter<T> { private: typedef intrusive_monitor self; ///< Self reference type protected: typename weak_intrusive_ptr<T>::monitor::handle _intrusive_monitor; ///< Our reference to the monitor public: /** Default constructor. The monitor is not allocated by this constructor, which means that @c get_weak_intrusive_ptr will return a non-functional instance until the monitor is initialized. @see init_intrusive_monitor */ intrusive_monitor() {} /// Construct with target to monitor. intrusive_monitor(T* target) : _intrusive_monitor(new weak_intrusive_ptr<T>::monitor(target)) {} /// Destructor. ~intrusive_monitor() { if (_intrusive_monitor) _intrusive_monitor->_target = 0; } /// Copy constructor. /// Don't copy monitor. intrusive_monitor(self const& that) {} /// Assignment. /// Don't copy monitor. self& operator = (self const& that) { return *this; } protected: /** Initialize the monitor. This is intended for use by classes that need to have initialization methods that are called from various constructors. This should only be called if the default constructor for this mixin is used. @return A reference to @c this. */ self& init_intrusive_monitor(T* target) { if (_intrusive_monitor) _intrusive_monitor->_target = target; else _intrusive_monitor = new monitor(target); return *this; } friend class weak_intrusive_ptr<T>; }; } // namespace boost

Alan M. Carroll:
Known issues: * May not handle cross assignment of target object. * No thread safety (not something I need, but should be thought about during Boostification)
Thread safety is a problem. Specifically, the race where thread A drops its intrusive_ptr and thread B converts its weak pointer to an intrusive_ptr. Thread B must end up with either NULL or an intrusive_ptr to a still valid object. I'm not seeing an easy way to achieve this using your current technique of using a member or a subobject whose destructor invalidates the weak pointers since once the destructor is entered, it is too late to prevent thread B from running away with its pointer to a zombie. :-) http://www.boost.org/doc/libs/1_36_0/libs/smart_ptr/sp_techniques.html#weak_... is similar and does have the same problem, only worse. :-)

My style is such that I don't share objects between threads because of all the potential for race conditions, so I rarely design with thread safety in mind. It seems to me there are two issues: 1) From the point of view of the monitor, its pointer is reset "by surprise", an arbitrary amount of time after the object is committed to destruction. 2) The object and the reference count are destructed simultaneously. I'll look at tweaking intrusive_release_ptr, which knows before the commit to destruct that it's going to happen. Perhaps warning the monitor might work. Additionally, we can detect the existence of weak pointers before destruction by looking at the reference count of the monitor. It might help to know that as well. At 03:12 PM 8/20/2008, you wrote:
Thread safety is a problem. Specifically, the race where thread A drops its intrusive_ptr and thread B converts its weak pointer to an intrusive_ptr. Thread B must end up with either NULL or an intrusive_ptr to a still valid object. I'm not seeing an easy way to achieve this using your current technique of using a member or a subobject whose destructor invalidates the weak pointers since once the destructor is entered, it is too late to prevent thread B from running away with its pointer to a zombie. :-)
participants (4)
-
Alan M. Carroll
-
Emil Dotchevski
-
Gennadiy Rozental
-
Peter Dimov