(A dynamic) Singleton using weak_ptr and shared_ptr

Hi all. I was faced with the requirement for a singleton class where the object has to be constructed dynamically and where I wanted to assure that the object is destructed automatically when no longer needed. A solution using shared_ptr and weak_ptr immediately came to mind and after a short google I found http://lists.boost.org/boost-users/2002/10/2014.php. However, if you have to assure that construction and destruction never overlap if you have multiple (sequential) initializations/destructions it gets a bit more complicated :) Well. Here follows what I have come up with and it seems to work pretty well. Any comments welcome. br, Martin Singleton using boost weak_ptr and shared_ptr --------------------------------------------- Requirement: Singleton that is constructed on first use (not on process start) and destroyed after the last "client-code" has finished with it. Note: It is therefore possible that more that one Singleton instances exist within a process's lifetime, BUT there must only be at most one Object active at any given time (Construction must not run before destruction has finished. Starting point: http://lists.boost.org/boost-users/2002/10/2014.php Problem of the simple solution: No protection against multiple initialization and against simultaneous deletion and construction. Solution: The construction and destruction of the singleton instance(s) has to be protected additionally. [CODE:dynamic_singleton.h] #pragma once ////////////////////////////////////////////////////////////////////// #include <boost/noncopyable.hpp> #include <boost/shared_ptr.hpp> #include <boost/scoped_ptr.hpp> ////////////////////////////////////////////////////////////////////// class dynamic_singleton : private boost::noncopyable { public: typedef boost::shared_ptr<dynamic_singleton> shared_t; static shared_t get_instance(); // Interface: void example(int cookie); // ... private: dynamic_singleton(); virtual ~dynamic_singleton(); struct impl; typedef boost::scoped_ptr<impl> impl_t; impl_t pimpl; struct deleter; friend struct deleter; }; [/CODE] [CODE:dynamic_singleton.cpp] #include "dynamic_singleton.h" #include <boost/weak_ptr.hpp> #include <boost/thread/recursive_mutex.hpp> #include <boost/thread.hpp> ////////////////////////////////////////////////////////////////////// #define MESG(msg) \ printf("%s\n", msg); \ /**/ ////////////////////////////////////////////////////////////////////// class atomic_bool : protected boost::noncopyable { public: atomic_bool() : b_(false) {} explicit atomic_bool(bool b) : b_(b) {} inline operator bool() const { boost::recursive_mutex::scoped_lock lock(sync_); return b_; } inline bool operator=(bool b) { boost::recursive_mutex::scoped_lock lock(sync_); b_ = b; return b_; } private: bool b_; mutable boost::recursive_mutex sync_; }; ////////////////////////////////////////////////////////////////////// struct dynamic_singleton::impl : private boost::noncopyable { impl() {} ~impl() {} static void start_construction() { boost::xtime spin_time; spin_time.sec = 1; while(the_object_exists) { boost::thread::sleep(spin_time); } } static void finish_construction() { assert(!the_object_exists); the_object_exists = true; } static void finish_destruction() { assert(the_object_exists); the_object_exists = false; } typedef boost::weak_ptr<dynamic_singleton> internal_shared_t; static internal_shared_t the_object; static boost::recursive_mutex sync_init; static atomic_bool the_object_exists; }; ////////////////////////////////////////////////////////////////////// dynamic_singleton::impl::internal_shared_t dynamic_singleton::impl::the_object; boost::recursive_mutex dynamic_singleton::impl::sync_init; atomic_bool dynamic_singleton::impl::the_object_exists; ////////////////////////////////////////////////////////////////////// struct dynamic_singleton::deleter { void operator() (dynamic_singleton* p) { assert(p); delete p; impl::finish_destruction(); } }; ////////////////////////////////////////////////////////////////////// dynamic_singleton::shared_t dynamic_singleton::get_instance() { // Syncronise Initialization: boost::recursive_mutex::scoped_lock lock(impl::sync_init); MESG(__FUNCTION__); // Acquire singleton pointer: shared_t object_ptr = impl::the_object.lock(); if(!object_ptr.use_count()) { impl::start_construction(); object_ptr.reset(new dynamic_singleton(), dynamic_singleton::deleter()); impl::the_object = object_ptr; impl::finish_construction(); } return object_ptr; } ////////////////////////////////////////////////////////////////////// dynamic_singleton::dynamic_singleton() { pimpl.reset(new impl()); MESG(__FUNCTION__); // For example open a unique system or process global resource printf(" >> Singleton opens the global resouce.\n"); } ////////////////////////////////////////////////////////////////////// dynamic_singleton::~dynamic_singleton() { MESG(__FUNCTION__); // For example close a unique system or process global resource printf(" << Singleton closes the global resouce.\n"); } ////////////////////////////////////////////////////////////////////// void dynamic_singleton::example(int cookie) { printf("%s(%d)\n", __FUNCTION__, cookie); } [/CODE] [CODE:main.cpp] #include "dynamic_singleton.h" #include <iostream> #include <boost/thread.hpp> struct singleton_user { explicit singleton_user(int num) : num_(num) { } void operator()() { using namespace std; printf("%d uses singleton ...\n", num_); dynamic_singleton::shared_t s = dynamic_singleton::get_instance(); s->example(num_); } int num_; }; int main(int argc, char* argv[]) { boost::thread t1( singleton_user(1) ); boost::thread t2( singleton_user(2) ); boost::thread t3( singleton_user(3) ); boost::thread t4( singleton_user(4) ); boost::thread t5( singleton_user(5) ); t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); return 0; } [/CODE]

Hi Martin, After a first glance it seems strange that you are only requesting the lock (sync_init) in get_instance(), which seems to be ok anyway (after quite a lot of shared_ptr/weak_ptr internals). Now I wonder whether the busy wait is needed or the combination of a mutex and a condition variable could be better, waking the waiting thread just when destruction of the object completes. Also I am not sure of the necessity of having both a start_construction() and finish_construction() that require two mutex acquisitions: // Using boost::condition: struct dynamic_singleton::impl : private boost::noncopyable { impl() {} ~impl() {} static void start_construction() { boost::recursive_mutex::scoped_lock lock( sync_ ); while ( the_object_exists ) { cond_.wait( lock ); } the_object_exists = true; } static void finish_destruction() { boost::recursive_mutex::scoped_lock lock( sync_ ); the_object_exists = false; cond_.notify_one(); } typedef boost::weak_ptr<dynamic_singleton> internal_shared_t; static internal_shared_t the_object; static boost::recursive_mutex sync_init; static boost::recursive_mutex sync_; // moved from atomic_bool static bool the_object_exists; // plain bool, synch'ed with sync_ static boost::condition cond_; }; dynamic_singleton::impl::internal_shared_t dynamic_singleton::impl::the_object; boost::recursive_mutex dynamic_singleton::impl::sync_init; boost::recursive_mutex dynamic_singleton::impl::sync_; bool dynamic_singleton::impl::the_object_exists = false; boost::condition dynamic_singleton::impl::cond_; Together with the rest of the code. As get_instance() warrants that there is at most one process trying to create the object, and all possibly deleting threads have already gone by (after the while loop in start_construction() ) changing the bool here or later is just the same. One other comment, I would mark both constructor and destructors for impl as private, since all methods and data are static there is no point in constructing objects of this class. This requires modifying dynamic_singleton class to remove the pimpl attribute. David

David Rodríguez Ibeas wrote:
Hi Martin,
After a first glance it seems strange that you are only requesting the lock (sync_init) in get_instance(), which seems to be ok anyway (after quite a lot of shared_ptr/weak_ptr internals).
The weak_ptr::lock function will only be useful when the weak_ptr already references something. For the first initialization I'm facing a race condition btw. different threads in that concurrent execution of weak_ptr::lock would yield multiple _empty_ shared_ptr references.
Now I wonder whether the busy wait is needed or the combination of a mutex and a condition variable could be better, waking the waiting thread just when destruction of the object completes.
Yes, I have been wondering if another synchronization method could be better, but (to my mind at least) I find the busy wait quite clear and I couldn't come up with anything more efficient/beautiful :)
Also I am not sure of the necessity of having both a start_construction() and finish_construction() that require two mutex acquisitions:
Hmmm ... yes, it seems it would not make any difference to have [the_object_exists = true;] at the end of start_construction. -- on the other hand, it's not constructed yet, so I would have to think of another name for the flag :)
// Using boost::condition: struct dynamic_singleton::impl : private boost::noncopyable { impl() {} ~impl() {} static void start_construction() { boost::recursive_mutex::scoped_lock lock( sync_ ); while ( the_object_exists ) { cond_.wait( lock ); } the_object_exists = true; } static void finish_destruction() { boost::recursive_mutex::scoped_lock lock( sync_ ); the_object_exists = false; cond_.notify_one(); } typedef boost::weak_ptr<dynamic_singleton> internal_shared_t; static internal_shared_t the_object;
static boost::recursive_mutex sync_init; static boost::recursive_mutex sync_; // moved from atomic_bool static bool the_object_exists; // plain bool, synch'ed with sync_ static boost::condition cond_; }; dynamic_singleton::impl::internal_shared_t dynamic_singleton::impl::the_object; boost::recursive_mutex dynamic_singleton::impl::sync_init; boost::recursive_mutex dynamic_singleton::impl::sync_; bool dynamic_singleton::impl::the_object_exists = false; boost::condition dynamic_singleton::impl::cond_;
Together with the rest of the code. As get_instance() warrants that there is at most one process trying to create the object, and all possibly deleting threads have already gone by (after the while loop in start_construction() ) changing the bool here or later is just the same.
Oh my. Yes this seems to work. After I have wrapped my head around the condition_variable usage pattern. :) (This solution seems to need more in-code comments though, or else I wont understand it two weeks from now :)
One other comment, I would mark both constructor and destructors for impl as private, since all methods and data are static there is no point in constructing objects of this class. This requires modifying dynamic_singleton class to remove the pimpl attribute.
Yes, you are right. In this example there's no point to constructing this class. cheers, Martin
participants (3)
-
David Rodríguez Ibeas
-
Martin T.
-
Martin Trappel