Re: Templates HowTo?
On Feb 21, 3:45 pm, ke...@bytebrothers.co.uk wrote:
On 21 Feb, 09:20, "Alf P. Steinbach" <al...@start.no> wrote:
* ke...@bytebrothers.co.uk:
Now this seems from my reading to be the sort of thing
that templates are probably good at, but I can't for the
life of me see how to do it, without first creating a
separate class for each of AAA, BBB, and CCC.
Each of group AAA, BBB and CCC essentially constitute a functor object.
Hi again. I'm going to try my luck one more time and ask for
some more advice on this!
Alf's suggestions upthread all worked swimmingly, but I'm left
with one thing that I can't quite work out.
The actual template is intended as a wrapper for the ciphers
in LibTomCrypt[1], and one of the things I need to do is find
out the 'index' of a particular cipher by passing the cipher
name as a string to a helper function.
That's usually done by maintaining a map of pointers to factory
functions (or factory functors). I use two different solutions,
depending on how dynamic it has to be:
The classical OO solution:
I use functors as the factories here, with pointers to
an abstract base class in the map. Something like:
// in Abstract.hh...
class AbstractType
{
public:
// ...
class Factory
{
public:
virtual ~Factory() {}
virtual AbstractType*
create() const = 0 ;
protected:
explicit Factory( std::string const&
key ) ;
} ;
static AbstractType*newInstance( std::string const&
key ) ;
private:
typedef std::map< std::string, Factory const* >
FactoryMap ;
static FactoryMap& getMap() ;
} ;
// in Abstract.cc...
Factory::Factory(
std::string const& key )
{
FactoryMap& map( getMap() ) ;
assert( map.find( key ) == map.end() ) ;
map.insert( FactoryMap::value_type( key, this ) ) ;
}
AbstractType*
AbstractType::newInstance(
std::string const& key )
{
FactoryMap const& map( getMap() ) ;
FactoryMap::const_iterator
factory( map.find( key ) ) ;
return factory == map.end()
? NULL
: factory->second->create() ;
}
FactoryMap&
Factory::getMap()
{
static FactoryMap* theOneAndOnly = NULL ;
if ( theOneAndOnly == NULL ) {
theOneAndOnly = new FactoryMap ;
}
return *theOneAndOnly ;
}
// in Concrete.hh
class ConcreteType : public AbstractType
{
// typically, *nothing* public !
private:
class ConcreteFactory ;
static ConcreteFactory
ourFactory ;
} ;
// in Concrete.cc
class ConcreteType::ConcreteFactory : public
AbstractType::Factory
{
public:
ConcreteFactory() ;
virtual AbstractType*
create() const ;
}
ConcreteType::ConcreteFactory
ConcreteType::ourFactory ;
ConcreteType::ConcreteFactory::ConcreteFactory()
: AbstractType::Factory( "Concrete" )
{
}
AbstractType*
ConcreteType::ConcreteFactory::create() const
{
return new ConcreteType ;
}
(Note that in this implementation, all of the
ConcreteFactory's must be registered before threading
starts. If this is not the case, then you need to grab a
lock before the if in getMap(), and getMap() should return a
boost::shared_ptr which releases it in the "destructor".)
In your particular case, you probably want to use
std::auto_ptr for the return types of create() and
newInstance(), since your abstract type is a polymorphic
agent, and not an entity object, which means that the client
is responsible for deleting it---it doesn't manage its own
lifetime. (For that matter, you might even use
std::auto_ptr for an entity object, to manage it until it
has enrolled for whatever events are relevant to its
lifetime.)
In order to add a concrete type to your application, just
link in Concrete. Dynamically, even. Say months after the
application has been running. (I use this technique a lot
in servers---you can add features without stopping the
server.)
The simple solution if you know "up front" all of the types that
will exist.
// in Abstract.hh...
class AbstractType
{
public:
// ...
static AbstractType*newInstance( std::string const&
key ) ;
} ;
// in Abstract.cc
namespace {
template< typename T >
struct StaticMap
{
char const* key ;
T value ;
class Match
{
public:
explicit Match( std::string const& target )
: myTarget( target )
{
}
bool operator()( StaticMap const* obj )
const
{
return myTarget == key ;
}
private:
std::string myTarget ;
} ;
} ;
typedef StaticMap< AbstractType* (*)() >
FactoryMap ;
template< typename Concrete >
AbstractType*
concreteFactory()
{
return Concrete ;
}
FactoryMap const map[] =
{
{ "AAA", &concreteFactory< AAA > },
{ "BBB", &concreteFactory< BBB > },
// ...
}
}
AbstractType*
AbstractType::newInstance(
std::string const& key )
{
FactoryMap const* factory
= std::find_if( begin( map ), end( map ),
FactoryMap::Match( key ) ) ;
return factory == end( map )
? NULL
: (*factory->factory)() ;
}
This has the advantage of simplicity, and since all static
data is statically initialized (notice the use of char
const* instead of std::string), there are no order of
initialization issues (nor threading issues, since all data
is const as well). It has the disadvantage of requiring all
of the concrete classes to be known in one place. This can
be partially attenuated by moving the actual map out to a
separate file, which is generated automatically by the make
file (e.g. from the list of source files of the concrete
classes).
I use this idiom often enough that StaticMap is part of my
standard library (along with begin() and end(), of course).
--
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