red floyd wrote:
Andrei Alexandrescu discusses this in "Modern C++ Design". It has to do
with memory architectures. In some cases, you need to do hardware
specific stuff to ensure cache coherency. He also recommends making
pinstance volatile.
I had to dig up my copy to check what you meant, and the only concern I
could see addressed is based on architectures that queue up memory
writes. Is that what you meant?
In a case like this, couldn't you rewrite:
Singleton* Singleton::makeSingleton()
{
Singleton* temp = 0;
try
{
ImplementationSpecificLock();
Singleton* temp = new Singleton;
ImplementationSpecificUnlock();
}
catch(...)
{
ImplementationSpecificUnlock();
throw;
}
return temp;
}
for those implementations that require it?
On a side note, how common are such architectures?
Andrei Alexandrescu doesn't actually describe the type of singleton I
use most often, which is kind of a variation on the phoenix singleton.
I'm not sure if the pattern I use has a name, but it requires explicit
lifetime control, allows for optional construction (lazy instantiation)
and external locking and checking.
template <typename T>
class Singleton
{
public:
typedef implementation_defined Mutex;
static Mutex& GetMutex() { return mutex_; }
static bool InstanceExists() { return !(instance_ = 0); }
static T& Instance()
{
if (!InstanceExists()) throw SingletonDoesNotExistException;
return *instance_;
}
protected:
explicit Singleton(T* t = 0)
{
Mutex::Lock lock(GetMutex());
if (instance_) throw MultipleSingletonException;
instance_ = t ? t : static_cast<T*>(this);
}
~Singleton()
{
Mutex::Lock lock(GetMutex());
instance_ = 0;
}
private:
static Mutex mutex_;
static T* instance_;
};
And used like this:
class Foo : public Singleton<Foo>
{
public:
void func1();
void func2();
};
The thinking I've always used behind this design is that I'd like to be
responsible for when the mutex is locked and unlocked, although
individual functions are free to lock it just in case (multiple locks
within a thread are ok, but each lock must be matched by an unlock). I'd
also like to be able to control when and how the singleton is created,
and potentially replace it on the fly in some cases.
For example:
// In this case, I use Foo if it is available
Foo::Mutex::Lock lock(Foo::GetMutex());
if (Foo::InstanceExists()) { /*use it*/ }
// I can also call multiple functions without relocking
Foo::Mutex::Lock lock(Foo::GetMutex());
Foo& foo = Foo::Instance();
foo.func1();
foo.func2();
// And I can swap objects on the fly
class Base : public Singleton<Base>
{
public:
virtual ~Base();
virtual void func1() = 0;
virtual void func2() = 0;
protected:
Base();
};
class Derived1 : public Base
{
public:
void func1();
void func2();
};
class Derived2 : public Base
{
public:
void func1();
void func2();
};
Base::Mutex::Lock lock(Base::GetMutex());
Base* base = &Base::Instance(); // Currently a Derived1
delete base;
base = new Derived2;
Base& new_base = &Base::Instance();
new_base.func1();
new_base.func2();
This doesn't seem to suffer from any instruction ordering problem to me,
and the cached-memory-write architecture problem could be handled in the
mutex class. How does this measure up?
mark