Re: Problem implementing an object factory
On Sep 12, 4:35 pm, Stephen Torri <sto...@torri.org> wrote:
On Mon, 10 Sep 2007 07:24:18 +0000, James Kanze wrote:
If he means that the user of the factory doesn't have to do
anything, the usual solution is to use the singleton idiom for
the factory (to avoid problems in order of initialization), and
then use static objects to register the the factories with the
map; if the map maps to functional objects, rather than to
pointers to functions, you need a static object for each type
anyway, so you might as well put the registration code in the
constructor.
Was this what you had in mind? I tried to create a singleton
using the example I found in Modern C++ Design.
Not really. You seem to have missed the reason why I suggested
a singleton.
In my own code, I use two different techniques for managing
dynamic factories, depending on the requirements. The most
frequent uses dynamic registration, and is something like:
class Object // but obviously, with a better, domain
{ // specific name...
// interface definition...
} ;
class AbstractFactory
{
public:
explicit AbstractFactory( std::string const&
name ) ;
virtual ~AbstractFactory() {}
Object* createObject() const = 0 ;
} ;
class DynamicFactory : private boost::uncopiable
{
public:
static DynamicFactory&
instance() ;
Object* create( std::string const& name ) const ;
void enrol( std::string const& name,
AbstractFactory const& factory ) ;
private:
DynamicFactory() ;
typedef std::map< std::string, AbstractFactory const* >
Map ;
Map myMap ;
} ;
AbstractFactory::AbstractFactory(
std::string const& name )
{
DynamicFactory::instance().enrol( name, this ) ;
}
DynamicFactory&
DynamicFactory::instance()
{
static DynamicFactory*
theOneAndOnly
= new DynamicFactory ;
return *theOneAndOnly ;
}
Object*
DynamicFactory::create(
std::string const& name ) const
{
Map::const_iterator entry = myMap.find( name ) ;
return entry == myMap.end()
? NULL
: entry->second->createObject() ;
}
void
DynamicFactory::enrol(
std::string const& name,
AbstractFactory const*
factory )
{
assert( myMap.find( name ) == myMap.end() ) ;
myMap.insert( Map::value_type( name, factory ) ) ;
}
DynamicFactory::DynamicFactory()
{
}
template< typename T >
class Factory : public AbstractFactory
{
public:
explicit Factory( std::string const& name )
: AbstractFactory( name )
{
}
Object* createObject() const
{
return new T ;
}
} ;
The implementation of each derived SomeObject then contains a
static variable:
namespace {
Factory< SomeObject >
f( "SomeObject" ) ;
}
Which objects the factory knows how to build is only determined
at link time. In fact, this can be made very dynamic, using
dynamic linking; just establish a naming convention between the
object and the dynamically linkable file which contains it, and
modify the create function so that it loads this file ("dlopen"
under Unix), then retries, if it doesn't find the entry in the
map. (I use this technique a lot for things like input
character translation: it allows me to add support for new
encodings long after the original program has been linked and
delivered.)
The disadvantage of this technique is that it constructs the
factory map dynamically, during initialization, so the factory
cannot be used in constructors of static objects. That's not
usually a problem, but if it is, some other solution must be
used. In such cases, I'll usually use functions, instead of
objects, for the individual factories, and a table with the
structure:
// Defined in DynamicFactory...
struct Map
{
char const* name ;
Object* (* create)() ;
bool operator==( Map const& other ) const
{
return name == other.name ;
}
bool operator!=( Map const& other ) const
{
return name != other.name ;
}
} ;
static Map myMap[] ;
static size_t myMapSize ;
// Automatically generated file:
#include "DynamicFactory.hh"
extern Object* createSomeObject() ;
extern Object* createAnotherObject() ;
// ...
DynamicFactory::Map DynamicFactory::myMap[] =
{
{ "SomeObject", &createSomeObject },
{ "AnotherObject", &createAnotherObject },
// ...
} ;
size_t DynamicFactory::myMapSize = N ;
// Note that the initializer N
// is generated automatically,
// by simply counting the
// entries as we generated
// them.
This file is easily generated in a makefile by means of a simple
AWK script, which is fed the list of object types (possibly in
the form of source file names---again, a naming convention is
necessary). The enrol function disappears, and the create
function becomes:
Object*
DynamicFactory::create(
std::string const& name ) const
{
Map const* entry
= std::find( myMap, myMap + myMapSize, name ) ;
return entry == myMap + myMapSize
? NULL
: (*entry->create)() ;
}
Obviously, this doesn't allow any form of dynamic linking,
although adding or removing an object type is a simple matter of
modifying the makefile (which must be done anyway), and doesn't
require any modification in existing code. And the table is
fully statically initialized, which means that it is guaranteed
to be initialized before any dynamic initialization
(non-trivial constructors of static objects) occur. Although
I've not actually needed this technique for a dynamic factory
yet, I've used it in a number of similar cases where I've needed
a mapping which was available during the initialization of
static objects (mapping enum values to names, initializing tries
for classifying UTF-8 characters, etc.).
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34