开发者

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.

  • Do I have repetitive code for all of th开发者_开发知识库e 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?


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...


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>();


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.


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;
}


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.


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;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜