Kris
Kris

Reputation: 65

C++: Create object of certain type mapped to an enum

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.

Upvotes: 3

Views: 7564

Answers (6)

Nim
Nim

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

peoro
peoro

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

sth
sth

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

CashCow
CashCow

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

user405725
user405725

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

tenpn
tenpn

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

Related Questions