Singleton

Singleton is a design pattern which describes a class which shall only have one instance during the runtime of a program. Such objects are very useful for creating interfaces for certain functionalities. For example a network connection handling is something that is a perfect example of a feature that can be hidden in a singleton.

In Transport Empire singletons are widely used to group a set of features under a common moniker. For example 'Vegetation Manager' is a singleton class which offers functionalities grouped around handling of trees, forests and such.

The usual approach to creation of singletons in C++ is using a static pointer to an instance within a class body like this:

// header
// define the singleton
class singleton
{
	static singleton * m_instance;
}

// source
// declare the field initial value
singleton * singleton::m_instance = 0;


This approach, while effective, is a chore to use because it requires a significant amount of boilerplate code to use and to safely create. Additionally, transferring such class between dynamic libraries requires even more boilerplate. In order to make it simpler Transport Empire offers a singleton class within the 'common' namespace.

#include
"common/singleton.hpp"

This class can transform any type to a singleton and offers an access to it through overloaded -> and * operators. The functionality is called opaque_singleton.

  1. Usage


To make a singleton of a class you need to.

Define your class

// in file forever_alone.hpp

#include
"common/singleton.hpp"

class forever_alone
{
public:
	forever_alone( int _friends );
	void weep();

// other declarations
};

typedef common::opaque_singleton forever_alone;
forever_alone_singleton; // syntactic sugar to avoid long lines

forever_alone.cpp

// normal class definitions follow

Somewhere in your code, preferably right at the beginning of 'main', manufacture an instance of the class

// in file main.cpp

int main( int _amount_of_arguments , char** _argument_table )
{
	boost::shared_ptr forever_alone_singleton;
	forever_alone_instance = forever_alone_singleton::manufacture( 0 );

// rest of code
}

now, anywhere in the code, assuming that an instace has been manufactured just use

// file some_random.cpp

// code...
// code...
forever_alone_singleton singleton;
singleton->weep();
// code...
// code...

To use the singleton in a given library you also need to link in the 'common' library.

  1. Details


Singleton template uses an approach which divides the actual singletons into two classes:
holders, manufactured with a static method 'manufacture'
accessors, which have overloaded the -> and * operators and allow to use the singletonized object.

  • Holders


Holders are classes which are created by calling the manufacture method of the singleton template:

template< typename _singletonized_class >
class opaque_singleton
{
	static
	boost::shared_ptr< opaque_singleton >
	manufacture();

	template< typename _1 >
	static
	boost::shared_ptr< opaque_singleton >
	manufacture( _1 const & _a1 )

	template< typename _1 , typename _2 >
	static
	boost::shared_ptr< opaque_singleton >
	manufacture( _1 const & _a1, _2 const & _a2 )
	
	template< typename _1, typename _2, typename _3 >
	static
	boost::shared_ptr< opaque_singleton >
	manufacture( _1 const & _a1, _2 const & _a2, _3 const & _a3 )
	
	template< typename _1, typename _2, typename _3, typename _4 >
	static
	boost::shared_ptr< opaque_singleton >
	manufacture(
			_1 const & _a1
		,	_2 const & _a2
		,	_3 const & _a3
		,	_4 const & _a4
		);
	
	template<
			typename _1
		,	typename _2
		,	typename _3
		,	typename _4
		,	typename _5
		>
	static
	boost::shared_ptr< opaque_singleton >
	manufacture(
			_1 const & _a1
		,	_2 const & _a2
		,	_3 const & _a3
		,	_4 const & _a4
		,	_5 const & _a5
		);
	
	template<
			typename _1
		,	typename _2
		,	typename _3
		,	typename _4
		,	typename _5
		, typename _6
		>
	static
	boost::shared_ptr< opaque_singleton >
	manufacture(
			_1 const & _a1
		,	_2 const & _a2
		,	_3 const & _a3
		,	_4 const & _a4
		,	_5 const & _a5
		,	_6 const & _a6
		);
}

Templated methods are used to create singletonized class' instance by calling an appropiate constructor ( with 1 to 6 parameters, more are possible if needed ).

Only one holder can exist at a given time. Attempt to create more will result in an exception being thrown.

Once a holder is destroyed, the instace of singletonized class is also destroyed.

  • Handles


Handles are the actual gateways to the singletonized class' instance. They are objects which are created with the default opaque_singleton constructor. During their lifetime they allow to use the access operators ( -> and * ) on them to gain access to the singletonized class' instance.

void some_function()
{
	common::opaque_singleton< int > total_instances_of_something_important;
	common::opaque_singleton< something_important > something_important_here;

	if( *total_instances_of_something_important == 0 )
	{
		something_important_here->panic();
	}
	else
	{
		something_important_here->make_one_more();
		++( *total_instances_of_something_important );
	}
}


Creation of a handle without a holder will cause an exception being thrown.

  • Internals


Internally the singleton class does a very questionable, but still somewhat safe thing. It does this:

void * stored_ptr
	=	reinterpret_cast< void * >( pointer_to_instance_of_singletonized_class );


Scared yet? To get the pointer back this is done:

singletonized_class * retrieved_ptr
	=	reinterpret_cast< singletonzied_class * >( retrieved_void_pointer );


This approach, while not very elegant, allows the instance to be stored without any type info by the internal storage. The storage and retrieval are done in a mirror image template methods which take the singletonized class as a template paramter. So while at first glace this is a suspect approach, it is actually quite safe. For details see "common/singleton.hpp".

  • Thread safety


This at the moment of writing this text was not tested, but inherently there is nothing to stop creation of handles concurrently if the singleton was linked with thread-safe standard library. Holder needs to be created in a critical section, obviously. Also, all singleton-related caveats are still true, so no double checked locking for you.

  1. Usage in TE


Transport Empire uses the opaque_singleton widely. All singletons are instantiated at the beginning of the application in the "application_starter" project. The instances are created in the runtime_objects class.