From: Christian P. <cp...@us...> - 2005-05-06 15:16:25
|
Update of /cvsroot/pclasses/pclasses2/include/pclasses In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv7519/include/pclasses Modified Files: Factory.h Log Message: - Rewrite of Factory<>. The new Factory eliminates the problem that client- and library code use different Factory instances. We address this problem by introducing a non-templated base class for the Factory template. Index: Factory.h =================================================================== RCS file: /cvsroot/pclasses/pclasses2/include/pclasses/Factory.h,v retrieving revision 1.21 retrieving revision 1.22 diff -u -d -r1.21 -r1.22 --- Factory.h 28 Apr 2005 10:35:39 -0000 1.21 +++ Factory.h 6 May 2005 15:16:01 -0000 1.22 @@ -1,535 +1,284 @@ -#ifndef p_FACTORY_H_INCLUDED -#define p_FACTORY_H_INCLUDED 1 -// Author: stephan beal <st...@s1...> -// License: Public Domain +/*************************************************************************** + * Copyright (C) 2005 by Christian Prochnow, SecuLogiX GmbH * + * cp...@se... * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Library General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ -#include <string> -#include <map> +// Based on the excellect Factory/Classloader code +// from stephan beal <st...@s1...> -#include <functional> +#ifndef P_Factory_h +#define P_Factory_h -#include <pclasses/Phoenix.h> // i don't like this dep, but i also don't like - // what happens in some cases when i don't use - // phoenix. :/ -#include <pclasses/SharingContext.h> #include <pclasses/Trace.h> +#include <pclasses/Export.h> +#include <pclasses/NonCopyable.h> +#include <pclasses/SharingContext.h> -namespace P -{ - /** - The Hook namespace holds classes intended to be used to - allow client-side code to hook in to the framework's - behaviour, replacing certain parts of the core with their - own. - */ - namespace Hook - { - /** - FactoryCreateHook is a helper object factory for the P::Factory - API. - - General conventions: - - SubT must derive from (or be) T and must be Default - Constructuable on the heap. In short, the following must be - able to succeed: - - <pre> - T * foo = new SubT; - </pre> - - Clients may freely specialize this type to hook their - factories in to P, and the above 'new' requirement need - not be imposed by client-side specializations. For example, - specializations are used to provide no-op factories for - abstract types, where 'new T' cannot work. - */ - - template < class T, class SubT > - struct FactoryCreateHook - { - /** - The type returned by create() and - operator(). - */ - typedef T * ResultType; - - /** - A typedef for the second template parameter for this - type. - */ - typedef SubT actual_type; - - /** - This creates a new SubT, which is assumed to be a - subclass of T. It can be used as a factory for - Factory & class_loader. - - If T or SubT are abstract types, you must - specialize this type such that create() returns 0 - for those. That is, we "simulate" creation of - abstract types by returning 0. - - The caller owns the returned pointer, which - may be 0. - */ - static ResultType create() - { - return new actual_type; - } - - /** - Same as create(). - */ - ResultType operator()() - { - return this->create(); - } - }; - - - /** - FactoryInstanceHook provides a way for non-core - code to manipulate or swap out the object returned - by Factory::instance(). - - Client code may want to specialize this to, e.g., plug in a - DLL-aware object lookups, auto-loading of plugins, etc. - - Specializing this type on a specific T will cause: - - MyFacT & fac = MyFacT::instance(); - - to trigger a call to operator()( fac ) the first - time fac is initialized AND on any re-initializations - (which may get called post-main()). - - - FactoryT is expected to be of type ::P::Factory<>, - but there is nothing in the implementation which is actually - specific to that type. - */ - template <typename FactoryT> - struct FactoryInstanceHook - { - typedef FactoryInstanceHook<FactoryT> ThisType; - - /** - Specializations of this type may initialize - instance() here. It will be called whenever Phoenix - initializes the object. See Phoenix for details. - - The default implementation does nothing. - */ - void operator()( FactoryT & ) throw() - { - } - - /** - The default implementation returns a shared Factory object. - THIS type's operator() will be called on the factory immediately - after creating the factory. - */ - static FactoryT & instance() - { - typedef ::P::Phoenix<FactoryT, ::P::Sharing::FactoryContext, ThisType > PHX; - return PHX::instance(); - } - }; - } // namespace Hook - - - /** - Factory is essentially a static classloader, capable of - loading classes by using registered factories for a given - set of keys (e.g., class names). - - Classloaders, at least in my experience, need to be able to - load all classes which derive from some given type. Without - a common base class, one can't safely attempt to cast from - an arbitrary pointer to the type we want to load. That's - where the InterfaceT parameter comes in. All objects - instantiated via this loader must inherit from InterfaceT. - - KeyType is a type which specifies the type of key used to - look up classes, defaulting to std::string. - - For this default implementation, both InterfaceT and - KeyType must be Default Constructable, and InterfaceT must - be constructable on the heap (e.g., via new - InterfaceT()). Clients supplying FactoryCreateHook or - FactoryInstanceHook specializations may apply other - requirements to InterfaceT and ImplT - - Thus InterfaceT must be a "plain type", without any pointer - or reference qualification. - - The default implementation holds no per-instance state, - thus it can be copied quickly. - - Sample usage: +#include <map> +#include <list> +#include <string> +#include <typeinfo> - <pre> -Factory<MyInterface> & fac = Factory<MyInterface>::instance(); -fac.registerFactory( "my_key", P::Hook::FactoryCreateHook<MyInterface,MyClass>::create ); -MyInterface *foo = fac.create( "some_key" ); // == NULL -foo = fac.create( "my_key" ); // == a new MyClass object - </pre> +namespace P { - Note that all instantiations of the same Factory type use - the same object factories map. The ContextType template - parameter can be used to limit the scope of a - Factory<InterfaceT>, such that it it does not collide with - other Factory<InterfaceT> instantiations. ContextType is - only used as a marker type, and is never instantiated by - this class. Used cleverly, it can allow you quite a bit - of freedom in what code has access to which factories. - */ - template < class InterfaceT, - class ContextT = Sharing::FactoryContext, - class KeyType = std::string - > - class Factory - { +//! Base class for the Factory template class +/*! + We need this base class to ensure that client- and library-code + will share the same Factory-instances. If we would put the instance- + creation code into a template class each compilation unit will use + it's own instance, leading to unwanted behaviour. +*/ +class PCORE_EXPORT FactoryBase: public NonCopyable { public: + //! Type for factory creation hook function + typedef FactoryBase* (*FactoryCreateFunc)(); + //! Type for type loader hook functions + typedef void (*LoaderFunc)(const std::string&, const std::string&, + const std::string&); - /** - A typedef for the KeyType used by this class. + //! Register a type loader hook + /*! + Loaders are called by the Factory class when no type is known + for a requested key. This allows non-core- and client-code to + register types with the Factory on their first use. + For example, the PluginManager makes use of this function to + dynamically load types from DLLs. + \param lf A pointer to a type-loader hook function. */ - typedef KeyType key_type; + static void registerLoader(LoaderFunc lf); - /** Same as ContextT parameterized type. */ - typedef ContextT ContextType; + protected: - /** - A typedef for the InterfaceT used by this class. - For conformance with the Adaptable Unary Functor - model - */ - typedef InterfaceT value_type; - /** - Same as (InterfaceT *). + //! Returns the Factory-instance for the given interface-type + /*! + This function returns the shared instance of a Factory object + for the given interface and context-type. If no existing instance + is found, a new one is created by a call to the given + FactoryCreateFunc. + Note that the type-strings are obtained by a call to + typeid(Type).name(). + \param ifaceType The interface for which the Factory should be + returned. + \param contextType The context for which the Factory should be + returned. + \param func A pointer to the function which gets called when the + Factory is created. */ - typedef InterfaceT * ResultType; + static FactoryBase& instance(const std::string& ifaceType, + const std::string& contextType, + FactoryCreateFunc func); - /** - Convenience typedef. + //! Runs all registered type-loaders + /*! + This method calls all registered type-loaders. + Note that the type-strings are obtained using a call to + typeid(Type).name(). + \param ifaceType The interface for which types should be loaded. + \param contextType The context in which types should be loaded. + \param key The requested key (class name) for the given interface. */ - typedef Factory< InterfaceT, ContextType, KeyType > ThisType; + static void loadType(const std::string& ifaceType, + const std::string& contextType, const std::string& key); - /** - A map type for storing lookup key aliases. - */ - typedef std::map<key_type,key_type> AliasMap; + FactoryBase(); + ~FactoryBase(); +}; +//! Type factory +/*! + Factory is essentially a static classloader, capable of loading classes + by using registered factories for a given set of keys (e.g., class names). - Factory() {} + InterfaceT must be default constructable. - virtual ~Factory() {} + Note that all instantiations of the same Factory type use the same object + factories map. The ContextT template parameter can be used to limit the + scope of a Factory<InterfaceT>, such that it does not collide with other + Factory<InterfaceT> instantiations. ContextT is only used as a marker + type, and is never instantiated by this class. Used cleverly, it can allow + you quite a bit of freedom in what code has access to which factories. +*/ +template <class InterfaceT, class ContextT = Sharing::FactoryContext> +class Factory: FactoryBase { + public: + typedef InterfaceT* (*TypeCreateFunc)(); - /** - The type of factories used by this class: a - function taking void and returning (value_type - *). See factoryMap(). + typedef std::map<std::string, TypeCreateFunc> FactoryMap; + typedef std::map<std::string, std::string> AliasMap; - todo: implement proper functor support. + //! Instantiate an object + /*! + Tries to instantiate an instance of InterfaceT using the given + key. Returns NULL if no object could be created for the given key. */ - typedef ResultType ( *FactoryFuncType ) (); + InterfaceT* create(const std::string& key) + { + std::string realKey = expandAlias(key); + typename FactoryMap::const_iterator i = + _factoryMap.find(realKey); - /** - Internal container type used for mapping keys to - factories. - */ - typedef std::map < key_type, FactoryFuncType > FactoryMap; + // try to load the requested type via the registered loaders... + if(i == _factoryMap.end()) + { + loadType(typeid(InterfaceT).name(), typeid(ContextT).name(), + realKey); + i = _factoryMap.find(realKey); + } - /** - Returns the map of classname aliases. - */ - AliasMap & aliases() - { - return ::P::Phoenix<AliasMap,ThisType>::instance(); + if(i != _factoryMap.end()) + return (i->second)(); + + return 0; } - /** - Returns the map of classname aliases. - */ - const AliasMap & aliases() const + void destroy(InterfaceT* obj) { - return ::P::Phoenix<AliasMap,ThisType>::instance(); + delete obj; } - /** - Aliases 'alias' as an equivalent of 'isthesameas'. - - This can be used to register multiple named via a - single factory. - */ - void alias( const key_type & alias, const key_type & isthesameas ) + //! Add an alias for a class-name + /*! + Adds an alias for a class-name. This can be used to register multiple + names via a single factory. Note that aliases are allowed to be + recursive. + \param aliasKey The alias that should be added. + \param isKey The key (class-name) that alias should expand to. + */ + void alias(const std::string& aliasKey, const std::string& isKey) { - this->aliases()[alias] = isthesameas; + P_TRACE(Factory) << "Register alias=" << aliasKey + << " for key=" << isKey; + + _aliasMap.insert(std::make_pair(aliasKey, isKey)); } - /** - Expands the given alias recursively. If a recursive alias is detected, - the last expansion is returned - i.e., the same as _alias. - */ - key_type expandAliases( const key_type & _alias ) const + //! Expand alias + /*! + Expands the given alias recursively. If a recursive alias is detected, + the last expansion is returned - i.e., the same as <alias>. + \param alias The alias that should be expanded. + */ + std::string expandAlias(const std::string& alias) const { - P_TRACE(Factory) << "expandAlias("<<_alias<<")"; - typename AliasMap::const_iterator cit = this->aliases().find( _alias ), - cet = this->aliases().end(); - if( cet == cit ) + P_TRACE(Factory) << "expandAlias("<<alias<<")"; + typename AliasMap::const_iterator cit = _aliasMap.find(alias), + cet = _aliasMap.end(); + if(cet == cit) { - P_TRACE(Factory) << "No alias found for '"<<_alias<<"'"; - return _alias; + P_TRACE(Factory) << "No alias found for '"<<alias<<"'"; + return alias; } - key_type exp = (*cit).second; - while( 1 ) + std::string exp = (*cit).second; + while(1) { - cit = this->aliases().find( exp ); - if( cet == cit ) + cit = _aliasMap.find(exp); + if(cet == cit) { - P_TRACE(Factory) << "Returning '"<<_alias<<"' unaliased: '" << exp<<"'"; + P_TRACE(Factory) << "Returning '"<<alias<<"' unaliased: '" << exp << "'"; return exp; } exp = (*cit).second; - if( exp == _alias ) - { // circular aliasing - P_TRACE(Factory) << "Warning: circular alias '"<<_alias<<"'."; + if(exp == alias) + { + P_TRACE(Factory) << "Warning: circular alias '"<<alias<<"'."; return exp; } } return exp; } - - /** - Tries to instantiate an instance of value_type - using the given key. Returns 0 if no object - could be loaded for the given key. - - Subtypes are free to implement, e.g., DLL lookups. - - This implementation calls expandAliases(_key) to - unexpand any aliased class names. Subclasses - "should" do the same, but are not strictly required - to. + //! Test if the factory provides a type + /*! + Returns true if the given key is registered. */ - virtual ResultType create( const key_type & _key ) // throw() ???? + bool provides(const std::string& key) const { - key_type key = this->expandAliases( _key ); - typename FactoryMap::const_iterator it = factoryMap().find( key ); - if ( it != factoryMap().end() ) // found a factory? + std::string realKey = expandAlias(key); + typename FactoryMap::const_iterator i = _factoryMap.find(realKey); + if(i == _factoryMap.end()) { - return ( it->second ) (); // run our factory. - } - return 0; - } - - - /** - Deletes obj. Subclasses are free to do custom - accounting, garbage collection, or whatever, by - overriding this. - - Note that it is not practical to expect all clients - to call this in order to destroy their objects, so - the utility of this function is highly arguable. + loadType(typeid(InterfaceT).name(), typeid(ContextT).name(), + realKey); - Subclasses are free to dictate a must-destroy() - policy if they wish. - */ - virtual void destroy( ResultType obj ) - { - delete obj; - } + i = _factoryMap.find(realKey); + } - /** - Returns this->create(key). - */ - ResultType operator()( const key_type & key ) - { - return this->create( key ); + return i != _factoryMap.end(); } - - /** - Registers a factory using the given key. If fp is - NULL then a default factory is used. Note that fp - may not return a type other than - ThisType::value_type *, but the actual object it - creates may be a polymorphic subclass of - value_type. See the FactoryCreateHook class for a - factory which does this subtype-to-base conversion. + //! Register a factory using the given key + /*! + Registers a factory using the given key. Note that fc may not + return a type other than InterfaceT *, but the actual object + it creates may be a polymorphic subclass of InterfaceT. */ - virtual void registerFactory( const key_type & key, FactoryFuncType fp ) - { - P_TRACE(Factory) << "registerFactory("<<key<<",facfunc)"; - factoryMap().insert( typename FactoryMap::value_type( key, fp ) ); - } - - template <typename TypeName> - void registerType( const key_type & key, TypeName ) + void registerFactory(const std::string& key, TypeCreateFunc fc) { - registerFactory( key, Hook::FactoryCreateHook<InterfaceT,TypeName>::create ); - } - - /** - Returns the internal key-to-factory map. It is safe - for clients to modify this except in multi-threaded - environments, and then all guarantees go out the - window. That said, it should never be necessary for - clients to use this. - - TODO: use a Hook or Traits type to allow the user - to replace this object with his own, so that he can - control the scope of the map more fully. - - - At the moment this returns the same object for all - instances of ThisType. Providing a private map from - each instance is under consideration. If we do that - we also will have to change aliasing support to - work the same. + P_TRACE(Factory) << "registerFactory(): key=" << key << ", fc=" + << typeid(fc).name(); - The reasoning behind using static factory maps is - essentially this: we can only have one definition - of each type. We normally want factories to always - return an instance constructed in the same way. If - we allow multiple factories per type we might - introduce hard-to-track inconsistencies in client - code, where different factories than intended are - accidentally used. OTOH, private factory maps - would open up some interesting possibilities. - */ - FactoryMap & factoryMap() - { - //return this->m_facs; - return Phoenix<FactoryMap,ContextType>::instance(); - } - const FactoryMap & factoryMap() const - { - //return this->m_facs; - return Phoenix<FactoryMap,ContextType>::instance(); + _factoryMap.insert(std::make_pair(key, fc)); } - /** - Returns true if the given key is registered. This is sometimes useful - for checking whether a factory needs to be re-registered, which - is sometimes necessary post-main(), when the internal map gets hosed - before clients are done using it. + //! Register a type using a default-factory + /*! + Registers a type using a default-factory implementation and the + given key. */ - bool provides( const key_type & key ) const + template <class TypeName> + void registerType(const std::string& key, TypeName) { - return factoryMap().end() != factoryMap().find( key ); + registerFactory(key, &typeCreateHook<TypeName>); } - /** - Returns a shared reference to a Factory. - - */ - static Factory & instance() + //! Returns the shared instance of the Factory + static Factory& instance() { - return Hook::FactoryInstanceHook<ThisType>::instance(); + return static_cast<Factory&>( + FactoryBase::instance(typeid(InterfaceT).name(), + typeid(ContextT).name(), &createHook)); } -// private: -// FactoryMap m_facs; - - }; // class Factory - - /** - NamedTypeFactory works for string-keyed types. It is - expected that this will be the most-used factory - implementation, as non-string-keyed factories are rare in - practice (but sometimes very useful). - A note to doxygen users: for some reason the parent class - isn't showing up via doxygen. :/ See Factory<InterfaceT>. - */ - template <typename InterfaceT> - struct NamedTypeFactory : public Factory< InterfaceT, Sharing::FactoryContext, std::string > - { - typedef Factory< InterfaceT, Sharing::FactoryContext, std::string > ParentType; - - typedef typename ParentType::ResultType ResultType; - typedef typename ParentType::FactoryFuncType FactoryFuncType; - - virtual ~NamedTypeFactory(){} - }; - - /* - Two helper macros to register types with the plugin manager... - added by cproch - */ - - //! Register type with NamedTypeFactory - #define NAMEDTYPEFACTORY_REGISTER_TYPE(b, c) \ - P::NamedTypeFactory<b>::instance().registerType(#c, c()) + private: + Factory() {} + ~Factory() {} - //! Register type-alias with NamedTypeFactory - #define NAMEDTYPEFACTORY_REGISTER_ALIAS(b, n, c) \ - P::NamedTypeFactory<b>::instance().registerType(n, c()) + static FactoryBase* createHook() + { return new Factory(); } - /** - The CL namespace encapsulates P's classloader-related API. + template <class TypeName> + static InterfaceT* typeCreateHook() + { return new TypeName(); } - All of the functions in this API use the object - NamedTypeFactory<InterfaceT>::instance() for - factory-related operations. Thus, using the various Hook - classes you can force these functions to use your factory. + FactoryMap _factoryMap; + AliasMap _aliasMap; +}; - i don't like this namespace. Maybe move these functions - into Factory itself??? They're simply proxying that type, - after all. - */ - namespace CL { - - using namespace ::P; +} // !namespace P - /** - Registers classname with InterfaceT. If factory_function is 0 - then ::P::Hook::FactoryCreateHook<InterfaceT,InterfaceT>::create is - used. If InterfaceT is abstract then you must specialize - ::P::Hook::FactoryCreateHook<InterfaceT,InterfaceT>, as documented - in FactoryCreateHook::create(). - */ - template <typename InterfaceT> - void registerBase( const std::string & classname, InterfaceT *(*factory_function)() = 0 ) - { - NamedTypeFactory<InterfaceT>::instance().registerFactory( classname, - ( 0 != factory_function ) - ? factory_function - : ::P::Hook::FactoryCreateHook<InterfaceT,InterfaceT>::create - ); - } - /** - Registers a factory creating ImplT objects with the - InterfaceT classloader. If factory_function is 0 then - ::P::Hook::FactoryCreateHook<InterfaceT,ImplT>::create is - used. - */ - template <typename InterfaceT, typename ImplT> - void registerSubtype( const std::string & classname, InterfaceT *(*factory_function)() = 0 ) - { - NamedTypeFactory<InterfaceT>::instance().registerFactory( classname, - ( 0 != factory_function ) - ? factory_function - : ::P::Hook::FactoryCreateHook<InterfaceT,ImplT>::create - ); - } - - /** - Returns the same as NamedTypeFactory<InterfaceT>::instance().create( classname ). - */ - template <typename InterfaceT> - InterfaceT * create( const std::string & classname ) - { - return NamedTypeFactory<InterfaceT>::instance().create( classname ); - } - } // namespace CL +//! Register type with the Factory +#define P_FACTORY_REGISTER_TYPE(Iface, c) \ + P::Factory<Iface>::instance().registerType(#c, c()) +//! Register alias with the Factory +#define P_FACTORY_REGISTER_ALIAS(Iface, a, c) \ + P::Factory<Iface>::instance().alias(#a, #c) -} // namespace P -#endif // p_FACTORY_H_INCLUDED +#endif |