Reputation: 65
Considering the following code example:
class Base;
class A; class B; class C; //A, B and C are inherited from Base
enum TypeID
{
TYPE_A = 0, TYPE_B, TYPE_C, TYPE_MAX;
}
class SomeContainer
{
private Base* objects[ TYPE_MAX ];
public void Add( TypeID what )
{
if( objects[ what ] ) return;
switch( what )
{
case TYPE_A: objects[ TYPE_A ] = (Base*) new A;
case TYPE_B: objects[ TYPE_B ] = (Base*) new B;
case TYPE_C: objects[ TYPE_C ] = (Base*) new C;
default: printf( "Invalid type\n" ); return NULL;
}
}
public A* GetA()
{
return (A*) objects[ TYPE_A ];
}
public B* GetB()
{
return (B*) objects[ TYPE_B ];
}
public C* GetC()
{
return (C*) objects[ TYPE_C ];
}
}
I think it is better than words to describe what my real system currently does.
Now in reality I have even more classes derived from base, it's about 15.
Do I have repetitive code for all of the classes derived from Base? As add a line for them in the switch statement, and an additional helper function to get them from the array?
Is there a way to automate this?
Upvotes: 3
Views: 7564
Reputation: 33655
If you're brave enough (or foolhardy - whichever way you look at it! ;) ) you could utilise boost mpl for this, here is a simple example..
#include <iostream>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/at.hpp>
namespace mpl = boost::mpl;
enum TypeID
{
TYPE_A = 0, TYPE_B, TYPE_C, TYPE_D
};
template <int N>
struct foo
{
foo() { std::cout << "foo<" << N << ">" << std::endl; }
};
template <int Index>
struct type_at
{
// this is the type container..
typedef typename mpl::vector<foo<0>, foo<1>, foo<2>, foo<3> > seq;
// this allows us to get the type at the given index
typedef typename mpl::at<seq, mpl::int_<Index> >::type type;
};
int main(void)
{
// define type based on the index.
type_at<TYPE_A>::type insta;
type_at<TYPE_B>::type instb;
type_at<TYPE_C>::type instc;
type_at<TYPE_D>::type instd;
return 0;
}
Upvotes: 0
Reputation: 26060
You should use a template to do your job.
Here an example of how I'd do that:
class Base {};
class A : public Base { public: static const int type = TYPE_A; };
class B : public Base { public: static const int type = TYPE_B; };
class C : public Base { public: static const int type = TYPE_C; };
template< typename T> void SomeContainer::Add( )
{
if( T::type >= TYPE_MAX ) {
printf( "Invalid type\n" );
}
if( objects[ T::type ] ) return;
objects[ T::type ] = new T;
}
Upvotes: 1
Reputation: 229583
Something along these lines should work:
class A; class B; class C;
template<class T>
struct type_index;
template<>
struct type_index<A> { static const size_t index = 0; };
template<>
struct type_index<B> { static const size_t index = 1; };
template<>
struct type_index<C> { static const size_t index = 2; };
template<class T>
public Base* Add() {
size_t index = type_index<T>::index;
if( !objects[index] )
objects[index] = new T;
return objects[index];
}
template<class T>
public T* Get() {
return (T*) objects[type_index<T>::index];
}
Then you can use cont.Add<A>()
to create the A
object, and cont.Get<B>()
to receive the B
object.
If it is actually a good idea depends on why you are trying to do this...
Upvotes: 5
Reputation: 31435
Similar to classic abstract factory pattern. Rough outline here:
class BaseFactory
{
public:
virtual ~BaseFactory();
virtual Base * createBase() const = 0;
}
template< typename T >
class BaseFactoryImpl : public BaseFactory
{
public:
Base * createBase() const
{
return new T;
}
};
std::map< key_type, BaseFactory * > baseFactoryTable;
// populate the map with instances of factories for the classes somewhere
// create an object
std::map< key_type, BaseFactory * >::const_iterator iter = baseFactoryTable.find(key);
if( iter!= baseFactoryTable.end() )
{
return iter->second->createBase();
}
// else throw or return NULL
In the case I see you are "getting" a class depending on an enumaration passed in. A way to add them all to the table would be to add an extra parameter to BaseFactoryImpl here to associate it with the enum for that class, then you can automatically get it to add itself to the map.
The purpose of using a factory to your type instead of your type is to handle your lazy-evaluation model whereby you create the actual instance only the first time it is used. In a multi-threaded environment you should use boost::once to do this.
Upvotes: 1
Reputation:
Kris, use abstract factory with polymorphism and templates. Here is something to start with. You can go even further by adding types to your factory from template type list.
Upvotes: 0
Reputation: 4706
Reduced some overhead using templates:
enum TypeID { ... } // as before
#define DECLARE_TYPE(_TYPEID) public: enum { myTypeID = _TYPEID }
// for each class
class A
{
DECLARE_TYPE(TYPE_A); // creates a public enum we can access
};
// B, C, D, etc
// Add() as normal, but only one Get() function:
template<typename T>
T* get()
{
return static_cast<T*>(objects[T::myTypeID]);
}
Usage:
B* const bInstance = get<B>();
Upvotes: 2