|
Placement New For Singleton Objects
Submitted by |
Often with objects following the singleton pattern, you will see them
implemented as follows:
// begin code
// header
class Singleton
{
public:
Singleton() {}
~Singleton() {}
static Singleton *get();
};
// CPP file
#include "Singleton.h"
static Singleton sSingleton;
Singleton *Singleton::get()
{
return &sSingleton;
}
// end code |
A big problem with this approach is due to the order of initialization of
global and static objects. According to the C++ standard, they are
initialized in the order listed in the source. But across translation units
(files), there are no guarantees about order of initialization.
The problem comes if some other global or static object is dependent on
Singleton for its construction. It will call Singleton::get(), and the
return value is not guaranteed to point to a valid, initialized object due
to order of initialization.
A common solution is to use lazy evaluation and allocate singleton objects
from the heap:
// begin code
// CPP file
#include "Singleton.h"
static Singleton *spSingleton=NULL;
Singleton *Singleton::get()
{
if(spSingleton == NULL)
{
spSingleton = new Singleton;
}
return spSingleton;
}
// end code |
This solves the order of initialization problem because the Singleton object
will be created the first time Singleton::get() is called. Astute readers
will notice that the Singleton will never be destroyed, but that can be
solved by judicious use of the std::auto_ptr<> class.
The lazy evaluation approach can be problematic, particularly for libraries,
because you are assuming that the application is using the default
implementations of new and delete. On embedded platforms such as a game
console, it is not uncommon for applications to use custom memory managers
and completely eschew the built in memory management.
The answer lies in a version of the new operator called placement new. It is
defined in the header file <new> and is part of the C++ Standard. Placement
new takes as an additional parameter an address to already allocated memory.
It properly calls the object's constructor, initializing the object in the
memory passed to it.
The following code demonstrates the use of placement new:
// begin code
// CPP file
#include "Singleton.h"
#include <new
template <class T class placement_auto_ptr
{
public:
placement_auto_ptr() : mp(NULL) {}
placement_auto_ptr &operator=(T *p)
{
mp = p;
return *this;
}
~placement_auto_ptr()
{
// can't call delete because it won't use placement delete
if(mp != NULL)
{
mp-T::~T();
}
}
operator T *() { return mp; }
T &operator*() { return mp; }
T *operator-() { return *mp; }
private:
T *mp;
};
static unsigned char sMem[sizeof(Singleton)];
static placement_auto_ptr<Singleton spSingleton;
Singleton *Singleton::get()
{
if(spSingleton == NULL)
{
// use placement new
spSingleton = new(sMem) Singleton;
}
return spSingleton;
}
// end code |
The use of placement_auto_ptr<> is optional, but recommended. For example,
if Singleton allocates OS resources you probably want the destructor called
to close them. Not that we do not use the delete operator, but instead call
the destructor explicitly. The reason is calling delete will call the
default implementation of delete, which will attempt to free memory that was
never allocated!
Placement new for singletons is not the answer for every situation, but it
is a useful item to have in your tool chest. It is particularly useful for
library authors who can not make assumptions about their client
application's memory management.
-Steve Anichini
|
The zip file viewer built into the Developer Toolbox made use
of the zlib library, as well as the zlibdll source additions.
|