Variant Factory
From codegorilla
Contents |
The C++ Variant Factory
To start with this is an example of how the boost variant framework (of which you should be familiar before using this code) can be used to create a class factory that can give classes unique identities and recreate those objects from the identities.
I have attempted to make is reasonably generic, it isn't ideal and should be treated more as a how to that a full working library.
My driving force for this development was for the transmission of lightweight classes over the network (in my case UDP), that had a simple identification for a class type and was easy to maintain and extend for other classes.
IMPORTANT: This library used an exception to create the instance in the factory - this is not ideal and I plan to do it another way in the future
The Source
This is a header only implementation and exists in just one file:
- variant_factory.h - contains the detail and the interface for using the variant factory.
The latest version is available via subversion, http://svn.codegorilla.co.uk/variant_factory/trunk/include
The Factory
The factory is implement in a class in the variant_helper namespace, by a template class called factory.
- variant_helper::factory< VT, IDTT >
Where VT is the variant typedef you wish the factory to manage and IDTT is the IDentity Type Trait (more on this later). The factory can work on nested variant types, these are variant that contain other variants, this allows for the user to contain more types in a variant than the definition of a variant actually allows (it can also help is categorising types and visitor operations)
This factory provided two static methods:
- identity_type identity( variant_type & type )
This returns the unique identity of the "active" object type in the variant as the identity type, this is a type specified by the IDTT.
- variant_type create( identity_type id )
This returns a variant type containing a new instance of the object identified by the id.
A simple default IDTT is provided by a class called simple_factory in which you specify the type that the identity should represent.
- variant_helper::simple_factory< VT, IT >
Where IT is the identity type, which is a built in type such as int. VT is the same as for the factory class.
Example
namespace example { // types squished together just for this example struct type_a_1 { type_a_1() {} }; struct type_a_2 {}; struct type_a_3 {}; struct type_b {}; struct type_c_a_1 { type_c_a_1() {} }; struct type_c_a_2 {}; struct type_c_b_1 { type_c_b_1 () {} }; struct type_d {}; typedef boost::variant<type_a_1,type_a_2,type_a_3> type_a; typedef boost::variant<type_c_a_1,type_c_a_2> type_c_a; typedef boost::variant<type_c_b_1> type_c_b; typedef boost::variant<type_c_a,type_c_b> type_c; // a 3 layer nested variant is created, each layer is 8 bits in id, so // the 32 bit (uint32_t) value will be able to hold the id typedef boost::variant<type_a,type_b,type_c,type_d> all_types; typedef variant_helper::simple_factory<all_types,boost::uint32_t> factory; struct nested_type : public boost::static_visitor<type_info const &> { template< typename T > type_info const & operator()( T const & ) const { return typeid(T); } template< BOOST_VARIANT_ENUM_PARAMS(typename T) > type_info const & operator() ( boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> const & sub_type ) const { return boost::apply_visitor( *this, sub_type ); } }; void run_example() { // note the extra brackets around type_c, this is intentional all_types any_type = type_c( type_c_a( type_c_a_1() ) ); boost::uint32_t id = factory::identity(any_type); all_types created = factory::create( id ); assert( boost::apply_visitor( nested_type(), any_type ) == boost::apply_visitor( nested_type(), created ) ); } }
The Rules
- The types in the variant must be default constructable for the factory to work correctly.
- The number of nested variants is dependent on the IDTT (and most likely the compiler).
- The identity of a class in a variant is position dependent, changing a types position will change its identity.
The IDTT (IDentity Type Trait)
The IDTT is responsible for the storing and creation of the location of a type in a variant and its conversion into a type relevant to your needs.
Its a trait that support two types and those types support some prerequisite interfaces that the factory requires to work with the nested variants.
A simple IDTT is supplied that can work with nested variants up to 8 levels dead and each level requires 8 bits for its identity, this IDTT (called id_trait_default takes a template type defining the combined identity type - a uint64_t can hold 8 levels).
The interface and types for the IDTT are shown below:
struct IDTT { struct index { index(); // must support the result type from variant::which // and is a zero base index into that variants items, // its maximum value depends on the number or template // parameters the variant can accept, in the sample I have // converted this into an 8 bit number, assuming no more // than 256 template parameters per level. index( int which_variant ); index& operator++(); index& operator--(); bool operator==(index const & rhs) const; }; // treat like a stack, the top id is the first level struct identity { identity(); // conversion to and from the external identity type // only required by the user of the factory // implicit conversion happens for the id_trait_default example // interface as defined for std::stack class index top(); void pop(); void push( index value ); }; };
