Boost logo

Boost :

From: Jason Hise (chaos_at_[hidden])
Date: 2005-07-28 15:25:39


This message is an in depth description of my current design plan for a
new singleton library, which I hope to complete and schedule for review
by boost in the coming year. I would appreciate any comments,
questions, or suggestions regarding this plan before I begin work on the
code. Specifically, I would like to know if there are any important
issues that this design fails to address, or difficulties with the
interface that would turn away potential users.

(My old singleton library, which was not accepted by boost, is available
at http://tinyurl.com/6qvrd. It was rejected primarily because it
failed to adequately address concerns related to multi threading and dlls.)

Thus far, my high level design plan looks like this:

The creator_policy defines fundamental pointer, const_pointer, and
reference types. These hold references to a creator policy instance
which maintains the singleton internally, so that the pointer types can
always see and access the state of the single instance. This way,
pointers to the singleton instance are not invalidated when the state of
the singleton changes. The creator_policy defines create, replace, and
destroy methods, as well as methods to generate instances of the various
pointer types. Create only creates the instance if it does not already
exist, destroy only destroys the instance if it does exist, and replace
makes sure any existing instance is destroyed before attempting
creation. Thus far, I hope to support the following creators:

--------------------------------------------------------------------------------

in_place_creator
creates/destroys inside an aligned_storage POD

interface:

create ( [ctor params] );
replace ( [ctor params] );
destroy ( );

pros: no memory allocation/deallocation ever required
      constructor params can be easily specified

cons: automatic creation, when it occurs, always uses the default
constructor
      singleton cannot be created as a derived type

useful for simple, user defined singleton classes

--------------------------------------------------------------------------------

source_sink_creator
owns fixed source and sink functors, specified with template parameters

interface:

create ( );
replace ( );
destroy ( );

pros: source can return a derived type
      automatic creation uses source rather than default construction

cons: source and sink cannot be set or changed at runtime
      constructor arguments cannot be specified by calling code

useful for singleton types provided by untouchable code, which may
require calling special functions to get pointers to instances

--------------------------------------------------------------------------------

dynamic_source_sink_creator
owns dynamic source and sink functors

interface:

create ( [source[, sink]] );
replace ( [source[, sink]] );
destroy ( [source[, sink]] );

pros: source can return a derived type
      automatic creation uses source rather than default construction
      source and sink can optionally be changed at run time
      source and sink can change functor types when they are set
      (example: from function pointer to lambda expression)

cons: specifying constructor parameters to pass is non-trivial
      dynamic memory allocation and virtual functions must be used to
store the functors
      in order to allow for different functor types at runtime, thus
creation is more
      expensive and can potentially fail

useful for a singleton which needs to take the forms of unspecified
derived types
and is frequently destroyed and created via some type of factory

--------------------------------------------------------------------------------

The lifetime_policy owns and wraps the creator policy, and provides
pointer and reference types which wrap those belonging to the creator.
It also provides functions to generate instances of the various pointer
types, and provides a function to access the creator_policy instance.
The unmanaged lifetime simply typedefs the underlying pointer and
reference types, and thus results in no overhead. The
longevity_lifetime and lifo_lifetime behave similarly, but handle
scheduling of automated destruction when any of the pointer generation
methods are called. The dependency_lifetime will wrap the internal
pointer types to perform reference counting (the reference count itself
will be stored in the lifetime_policy instance). The timeout_lifetime
will manage a separate thread that releases the instance when it
finishes counting down. It will also wrap the internal pointer types to
reset the timer when a pointer is dereferenced, and reset and lock the
timer while any references exist.

The threading_policy owns and serializes access to the lifetime_policy.
It also has a function to get access to the lifetime_policy instance,
and provides pointer, const_pointer, and reference types which wrap and
forward to the lifetime's corresponding types. For the single_threaded
policy, these pointer types are simply typedefs of the lifetime's
pointer types, resulting in no overhead, and the function through which
the lifetime_policy instance is accessed just returns a raw pointer to
it. For the multi_threaded policy, pointer types perform locking before
forwarding to lifetime pointer types, and the reference type acts as a
lock throughout it's existence. The mutex is stored inside the
threading_policy instance. The function which returns a pointer to the
lifetime_policy instance returns a smart pointer which serializes access.

A storage_policy determines how instances of the threading_policy are
stored (and because these instances contain instances of the other
policies, this indirectly controls how all of the policy instances are
stored). static_storage stores policy instances directly as global
variables, which is fine for singletons that are not required to have a
specific lifetime with regard to other singletons. These global
variables will be set up in such a way that it will be guaranteed that
any automatic destruction takes place before the policy bundle is
destroyed. group_storage allows multiple singletons to have their
policies stored in a master group. No policies in this group are
destroyed until all automated destruction that will be performed by any
members of the group has completed. This way, if destructors of some of
the singletons in this group trigger re-creation of other singletons in
the same group, the policies needed to recreate the singletons are
guaranteed to still exist. dll_storage requires linking with
singleton.dll, and stores instances in a special exported manager
singleton to ensure unique singleton instances across dll boundaries.
All policy instances of this type are guaranteed to be destroyed only
after all automated destruction has taken place.

A singleton class exists, which takes a singleton type and a name tag as
template parameters. The name will have a default type called
'unnamed'. This name parameter enables using multiple singleton
instances of the same type. Policies are traits of the name specified,
so using the default name 'unnamed' results in using the default
policies. Policies can be specified by declaring a name as an undefined
struct, and then specializing the desired policy traits for that name.

The singleton class contains three pointer types, which wrap those of
the threading policy. These are strong_pointer, lazy_pointer, and
weak_pointer. strong_pointer attempts automatic creation of the
singleton instance if it does not exist when the pointer is first
created, and also attempts automatic creation if needed any time it is
dereferenced. lazy_pointer only attempts automatic creation when
dereferenced, and weak_pointer will never create the instance
automatically. For the lifo_lifetime, a strong_pointer can be used to
specify a dependency of some singleton B on some singleton A by having B
own a strong_pointer to A. This will ensure that A is created before B
and destroyed after B.

Const versions of these three pointer types will also exist. Creation,
destruction, and non-const member functions will not be allowed to be
called through these const pointer types. A single reference type will
exist which will be the result of dereferencing any pointer type via the
unary * operator. const_pointers return const qualified references.
Pointers only perform temporary locking (if the threading policy
supports locking) when a member function is called via the -> operator
or when a create or destroy operation is invoked. References lock the
singleton throughout their entire lifetime, and thus can be used to make
a series of operations on the singleton instance atomic. A reference
provides an implicit conversion to a raw reference of the singleton's
type (which comes back const if the reference is const). Create and
destroy can only be called on non-const references.

Typical code will access a singleton instance by creating one of the
above pointer types and calling member functions through it via the ->
operator. Ex:

singleton < MyType, SomeName >::lazy_pointer ptr;
ptr->DoSomething ( );

--------------------------------------------------------------------------------

I plan to sneak an improved version of the null developed by Scott
Meyers in with this code in a header called boost/null.hpp, and use it
in the singleton's implementation. It will be defined as follows:

namespace boost
{
    const class
    {
    private:
        void operator & ( ) const;

    public:
        #ifndef BOOST_NO_MEMBER_TEMPLATES

        // provide conversion to any pointer type
        template < typename Class, typename Type >
        operator Type Class::* ( ) const { return 0; }

        template < typename Type >
        operator Type * ( ) const { return 0; }

        #else

        //provide conversion to int if member templates are not supported
        operator int ( ) const { return 0; }

        #endif//BOOST_NO_MEMBER_TEMPLATES

    } null;
}

#undef NULL
#define NULL (::boost::null)

--------------------------------------------------------------------------------

Any and all comments or suggestions on this plan are welcomed.
Potential problems are much easier to address at design time than after
the code is written.

-Jason


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk