From: stephan b. <sg...@us...> - 2004-12-23 04:46:02
|
Update of /cvsroot/pclasses/pclasses2/include/pclasses In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv18827 Added Files: Phoenix.h Factory.h Log Message: egg --- NEW FILE: Factory.h --- #ifndef p_BASICFACTORY_H_INCLUDED #define p_BASICFACTORY_H_INCLUDED 1 // Author: stephan beal <st...@s1...> // License: Public Domain #include <string> #include <map> #include <functional> #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. :/ namespace P { /** Namespace Sharing holds internal "sharing context" marker classes. */ namespace Sharing { /** Internal marker class. */ struct FactoryContext {}; } /** 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 requirement may not be imposed by client-side specializations. */ template < class T, class SubT > struct FactoryCreateHook { /** The type returned by create() and operator(). */ typedef T * result_type; /** 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 result_type create() { return new actual_type; } /** Same as create(). */ result_type operator()() const { return 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()). */ 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. Both InterfaceT and KeyType must be Default Constructable, and InterfaceT must be constructable on the heap (e.g., via new InterfaceT()). The default ipmlementation holds no per-instance state, thus it can be copied quickly. Sample usage: <pre> typedef Factory<MyClass> CL; CL::register_factory( "my_key" ); MyClass *foo = CL::load( "some_key" ); // == NULL foo = CL::instantiate( "my_key" ); // == a new object </pre> Note that all instantiators of the same type use the same object factories. The ContextType template parameter can be used to limit the scope of the object factory registrations to a specific context: instantiators with different Contexts use different maps. ContextType is only used as a type, and is never instantiated by this class. */ template < class InterfaceT, class KeyType = std::string, class ContextType = Sharing::FactoryContext > class Factory { public: /** A typedef for the InterfaceT used by this class. */ typedef InterfaceT value_type; /** A typedef for the KeyType used by this class. */ typedef KeyType key_type; /** Same as ContextType */ typedef ContextType context_type; /** Same as KeyType, for conformance with the the Adaptable Unary Functor model. */ typedef KeyType key_type; /** Same as (InterfaceT *), for conformance with the Adaptable Unary Functor model. */ typedef InterfaceT * result_type; /** Convenience typedef. */ typedef Factory< InterfaceT, KeyType, ContextType > ThisType; Factory() {} virtual ~Factory() {} /** The type of factories used by this class: a function taking void and returning (value_type *). See factoryMap(). todo: implement proper functor support. */ typedef result_type ( *factory_type ) (); /** Internal container type used for mapping keys to factories. */ typedef std::map < key_type, factory_type > FactoryMap; /** returns instance().internal_instantiate( key ). The caller takes responsibility for the returned pointer. */ static result_type instantiate( const key_type & key ) { return instance().internal_instantiate( key ); } /** Returns this->internal_instantiate(key). */ result_type operator()( const key_type & key ) { return this->internal_instantiate( key ); } /** 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. */ void registerFactory( const key_type & key, factory_type fp ) { factoryMap().insert( FactoryMap::value_type( key, fp ) ); } /** 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. It is safe to call this post-main(), but such calls may return an empty map! */ static FactoryMap & factoryMap() { return Phoenix<FactoryMap,ThisType>::instance(); } /** 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. */ bool isRegistered( const key_type & key ) const { return factoryMap().end() != factoryMap().find( key ); } /** Returns a shared reference to a Factory. Client code may plug in a new default instance() by specializing Hook::FactoryInstanceHook< FactoryT >. See that type for details. */ static Factory & instance() { return Hook::FactoryInstanceHook<ThisType>::instance(); } protected: /** Tries to instantiate an instance of value_type using the given key. Returns NULL if no class could be loaded for the given key. This is the virtual equivalent to the static instantiate(). Subtypes are free to implement, e.g., DLL lookups. */ virtual result_type internal_instantiate( const key_type & key ) { typename FactoryMap::const_iterator it = factoryMap().find( key ); if ( it != factoryMap().end() ) // found a factory? { return ( it->second ) (); // run our factory. } return 0; } }; // 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). */ template <typename InterfaceT> struct NamedTypeFactory : public Factory< InterfaceT, std::string, Sharing::FactoryContext > { virtual ~NamedTypeFactory(){} }; /** The CL namespace encapsulates P's classloader-related API. All of the functions in this API use the obbject NamedTypeFactory<InterfaceT>::instance() for factory-related operations. Thus, using the various Hook classes you can force these functions to use your factory. */ namespace CL { using namespace ::P; using namespace ::P::Sharing; /** 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().instantiate( classname ). */ template <typename InterfaceT> InterfaceT * classload( const std::string & classname ) { return NamedTypeFactory<InterfaceT>::instance().instantiate( classname ); } } // namespace CL } // namespace P #endif // p_BASICFACTORY_H_INCLUDED --- NEW FILE: Phoenix.h --- #ifndef p_PHOENIX_HPP_INCLUDED #define p_PHOENIX_HPP_INCLUDED 1 //////////////////////////////////////////////////////////////////////////////// // Phoenix.hpp // Phoenix<> provides "context singletons" with "phoenixing" capabilities. // // Author: stephan beal <st...@s1...> // License: Public Domain // CVS Revision: $Revision: 1.1 $ //////////////////////////////////////////////////////////////////////////////// #include <stdlib.h> // atexit() #include <iostream> // cout/cerr #ifndef phoenix_DEBUG // enable debuggering to see when phoenixes are (re)created. # define phoenix_DEBUG 0 #endif #if phoenix_DEBUG # include <typeinfo> # define phoenix_CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : " \ << "Phoenix<"<<typeid((base_type *)NULL).name()<<" , " \ << typeid((context_type *)NULL).name()<<"> " #else # define phoenix_CERR if(0) std::cerr #endif // phoenix_DEBUG /** The Phoenix class acts as a wrapper for adding "phoenixing" behaviour to arbitrary shared objects, as covered in detail in Alexandrescu's "Modern C++ Design". */ namespace P { /** Internal helper class to provide a default no-op initializer for phoenixed objects. See the Phoenix<> class. */ struct no_op_phoenix_initializer { /** Does nothing: This class is called no_op for a reason ;) */ template <typename T> void operator()( T & ) throw() { return; } }; /** Phoenix is class for holding singleton-style instances of BaseType objects. Rather than requiring that BaseType be a Singleton type, Phoenix subclasses BaseType to add the Phoenix-like capabilities. Phoenixing makes the shared object post-main() safe, in terms of object destruction order. Parameterized on: - BaseType: must be struct or class type and must be default-constructable. i have no clue what is supposed to happen if BaseType's dtor is not virtual. That said, Phoenix has been successfully demonstrated with a BaseType of std::map, which has no virtual dtor. - ContextType: These objects are only singletons within the given ContextType. That is, Phoenix<T,X>::instance() will return a different object than Phoenix<T,Y> will. - InitializerType: must be a unary functor accepting a BaseType &. It's return value is ignored. The default functor does nothing. The InitializerType is called when a to-be-phoenixed object is initially created and whenever it is phoenixed. This is intended to be used, e.g., for re-populating a phoenixed shared object. TODO: investigate the implications of a predicate initializer, which would return false if the object could not be initialized. InitializerType::operator() must not throw. Whether or not BaseType is technically a singleton depends on entirely BaseType itself. This class is more often used to provide easy access to context-dependent shared objects, rather than pure singletons. The Phoenix class itself is a true Singleton, but each combination of template arguments provides a different Singleton *type*, so the end effect is "context singletons." This is another attempt to solve the classic Keyboard-Console-Log problem, as discussed at length in <i>Modern C++ Design</i>. It relies on sane behaviour in the C library's atexit() function, which, as is shown in MC++D, is not the case on all systems. That said, the Phoenix-specific behaviours are undefined on those systems, which is only to say that it might not be post-main() safe. Caveats: i am not 100% clear on all of the implications of this implementation's approach... my gut tells me i'm missing some significant bits. i mean, it <i>can't</i> have been this straightforward to solve ;). The very nature of the Phoenix Singleton problem makes it difficult to reliably test in real-world applications. That said, i have seen a objects be successfully phoenixed and atexit()ed, so it is known to at least "basically" work. There's a paper about "context singletons", this class, and some of it's implications, at: http://s11n.net/misccode/context_singletons.html [Much later: i've gotten more re-use out of this class than probably any other single class i've ever written.] */ template < typename BaseType, typename ContextType = BaseType, typename InitializerType = no_op_phoenix_initializer > struct Phoenix : public BaseType { /** context_type is unused by this class, but might be useful for type identification at some point. */ typedef ContextType context_type; /** The BaseType parameterized type. */ typedef BaseType base_type; /** The functor type used to initialize this phoenixed object. */ typedef InitializerType initializer_type; /** Returns a shared instance of this object. The instance() method will always return the same address, though it is potentially possible (post-main()) that the actual object living at that address is different from previous calls. It is never a good idea to hold on to the returned reference for the life of an object, as that bypasses the phoenixing capabilities. If you ever delete it you're on you're own. That's a Bad Idea. */ static base_type & instance() { static this_type meyers; static bool donethat = false; if( this_type::m_destroyed ) { phoenix_CERR << "Phoenixing!" << std::endl; donethat = false; new( &meyers ) this_type; atexit( this_type::do_atexit ); } if( !donethat ) { phoenix_CERR << "initializing instance" << std::endl; donethat = true; initializer_type()( meyers ); } phoenix_CERR << "instance() == " <<std::hex<<&meyers<<std::endl; return meyers; } private: /** A convenience typedef. */ typedef Phoenix<base_type,context_type,initializer_type> this_type; static bool m_destroyed; Phoenix() { phoenix_CERR << "Phoenix() @" << std::hex<< this << std::endl; m_destroyed = false; } virtual ~Phoenix() { phoenix_CERR << "~Phoenix() @" << std::hex<< this << std::endl; m_destroyed = true; } /** Destroys the shared object via a manual call to it's dtor. */ static void do_atexit() { if( m_destroyed ) return; phoenix_CERR << "::do_atexit() @ " << std::hex << &instance() << std::endl; static_cast<this_type &>(instance()).~Phoenix(); // will eventually trigger the BaseType dtor } }; template <typename T, typename C, typename I> bool Phoenix<T,C,I>::m_destroyed = false; } // namespace #undef phoenix_DEBUG #undef phoenix_CERR #endif // p_PHOENIX_HPP_INCLUDED |