Originally posted on Stack Overflow.
So far, I've used this in a few places and it seems solid.
This is what I usually do to make a singleton:
// Assume x86 for now
#ifdef _MSC_VER
#include <intrin.h> // for _mm_pause
#else
#include <xmmintrin.h> // for _mm_pause
#endif
#include <cstdint>
#include <exception>
#include <atomic>
#include <memory>
// When thrown by get(), this indicates the singleton failed to initialize.
//
// It is better to devise a more elaborate set of exceptions for diagnostic purposes
// (e.g. If it must be initialized in the main thread only, etc...).
class bad_singleton : public std::exception{};
template<typename singleton_t>
class singleton
{
// Should start life as false, no initialization logic needed
// (unless working with a brain-dead compiler, in which case
// we're screwed anyway).
static bool _bad_singleton;
// This should also be 0 on start, but some platforms might require
// a constructor that could fudge things if it runs while the lock is
// being used (meaning that the platform requires kernel objects for all atomic
// operations). To make this robust, use a plain uintptr_t here and
// OS-provided atomic functions (e.g. _InterlockedExchange[64]), If atoms aren't
// supported, you shouldn't be using threads anyway.
static std::atomic<uintptr_t> _spinlock;
// Once again, shared_ptr might require initialization logic
// that could run after the allocation in get() [assuming we have things
// running before main]. To make this robust, use a pointer here
// and OS-provided atomic functions.
static std::shared_ptr<singleton_t> _handle;
public:
static std::shared_ptr<singleton_t> get()
{
// Assumes acquire semantics on read.
if(_handle){
// _handle is nonzero? Instant return!
return _handle;
}else{
// Every thread that found _ptr to be null will end up here
// and spin until the lock is released.
while(_spinlock.exchange(1))
{
// One can use system calls here (like Sleep(0) on Windows) if desired
//
// Otherwise, this has a similar effect without dragging in too much
// platform-specific gunk.
_mm_pause();
}
// Only one thread at a time here, so check once more and return
// (another thread could have finished constructing the instance).
if(_handle){
_spinlock.exchange(0);
return _handle;
}
if(_bad_singleton){
// The singleton failed to initialize in another thread.
// EDIT: Forgot to release the lock
_spinlock.exchange(0);
throw bad_singleton();
}
// Since it is assumed that the constructor does something
// simple or nothing at all, the only overhead here is
// having to allocate space on the heap. This is also
// why singletons like this should be made initially
// as small as possible in order to reduce the probability
// of an allocation failure.
singleton_t *_frob = nullptr;
try{
_frob = new singleton_t();
// EDIT: Prevent possible premature _handle initialization
// forces new singleton_t() to finish execution before
// initializing _handle.
//
// It is also assumed that the singleton's constructor
// returns only when the instance is fully initialized.
_WriteBarrier(); //<- or equivalent
std::shared_ptr<singleton_t> _derp(_frob);
// EDIT: Shared_ptr allocates a control block, so
// we could still have a garbage pointer if directly
// constructed into _handle.
_WriteBarrier();
// This should not do anything that throws an exception.
//
// In fact, this should be atomic (It can be made so in a custom
// handle implementation).
_handle = move(_derp);
}catch(...){
// Diaper has been soiled...
// EDIT: Forgot that shared_ptr allocates a control block.
//
// Is possible to have _frob without _handle...
if(_frob)delete _frob;
// There is probably a way to reproduce this exception
// for all other threads, but for now, just throw a
// bad_singleton exception everywhere else.
_bad_singleton = true;
_spinlock.exchange(0);
// Throw whatever was caught for logging.
throw;
}
// Now release the spinlock to let any contending threads in.
_spinlock.exchange(0);
return _handle;
}
}
};
template<typename singleton_t>
bool singleton<singleton_t>::_bad_singleton;
template<typename singleton_t>
std::atomic<uintptr_t> singleton<singleton_t>::_spinlock;
template<typename singleton_t>
std::shared_ptr<singleton_t> singleton<singleton_t>::_handle;
class my_singleton
{
protected:
// allows the singleton wrapper to use the constructor
friend class singleton<my_singleton>;
my_singleton()
{
// Very simple things in here
//
// Split singletons into related subsystems
// that can be initialized when required,
// but in a controlled way through the instance
// itself (e.g. allocate a kernel object and
// use that for serializing initializations of other
// systems that take longer or are more complex).
}
private:
// No duplicating or moving this singleton whatsoever
my_singleton &operator=(const my_singleton &);
my_singleton &operator=(my_singleton &&);
my_singleton(const my_singleton &);
my_singleton(my_singleton &&);
public:
// Destructor is out here in the public because shared_ptr needs it.
//
// A custom smart handle implementation could allow for private destructors.
~my_singleton()
{
// Note that this will only get called
// when the last shared_ptr has been destroyed.
//
// Note also that _handle will keep the singleton
// alive when there are no handles elsewhere
// during execution.
}
};
int main(int argc, char **argv)
{
std::shared_ptr<my_singleton> hsingleton;
try{
hsingleton = singleton<my_singleton>::get();
}catch(bad_singleton &){
// blah initialization error
}catch(...){
// blah all other errors
}
return 0;
}
Using a template wrapper avoids the complexities associated with inheriting from "singletons" that implement their own initialization logic.
To me, it seems this can't break if used within one process.
if(_handle)...andif(_bad_singleton).... – i_photon Jul 22 '12 at 4:31