Frage

Im trying to create a system capable of allocating any type, and grouping same types together in arrays.

I want to be able to retrieve each array later using so I can iterate over each type.

Something like this:

ObjectDatabase
{
   template<typename T>
   T* Allocate();

   template<typename T>
   Array<T>& GetObjects();
}

My Array type is actually a pool so allocation/deletion is fast.

I thought about mapping each Array in a std::map using an int representing the type id for each T, but then each type T would need to inherit from a base class, so it can be stored in the map, and thus leading to casting when I iterate over the array.

I think this pattern has been done before but I'm not sure how.

Can someone help?

Update:

So I'm trying to basically create a structure like this:

struct ObjectDatabase
{
    Array<Entities> mEntities;
    Array<Transforms> mTransforms; 
    Array<Physics> mPhysics; 
    Array<Graphics> mGraphics; 
}

But I wanted to somehow create the set of arrays at compile time.. using templates?

Then provide template functions to get access to each array, and to allocate from each array

War es hilfreich?

Lösung

You probably want to use templates to do type elision. Here's an example that may be similar to what you're looking for. The ObjectDatabase class uses templates and polymorphism internally to do type elision so the classes used don't have any constraints on them (other than the normal constraints for being placed in a standard library container).

#include <iostream>
#include <typeinfo>
#include <deque>
#include <map>
#include <cassert>
using namespace std;

struct ObjectDatabase {
    ObjectDatabase() { }

    template<typename T>
    T &allocate() {
        deque<T> &a = getObjects<T>();
        a.push_back(T());
        return a.back();
    }

    template<typename T>
    deque<T> &getObjects() {
        CollectionBase *&el = m_obdb[typeid(T).name()];
        if ( not el )
            el = new Collection<T>();
        Collection<T> *elc = dynamic_cast<Collection<T>*>(el);
        assert(elc);
        deque<T> &a = elc->elements;
        return a;
    }

    ~ObjectDatabase() {
        for ( ObDB::iterator i=m_obdb.begin(); i!=m_obdb.end(); ++i)
            delete i->second;
    }
private:
    ObjectDatabase(ObjectDatabase const &);
    ObjectDatabase &operator=(ObjectDatabase const &);

    struct CollectionBase {
        virtual ~CollectionBase() { }
    };
    template<typename T>
    struct Collection : CollectionBase {
        deque<T> elements;
    };
    typedef map<string, CollectionBase *> ObDB;
    ObDB m_obdb;
};

struct Foo {
    Foo() : name("Generic Foo") { }
    char const *name;
};

struct Bar {
    string name;
};

int main() {
    ObjectDatabase obdb;
    obdb.allocate<Foo>().name = "My First Foo";
    obdb.allocate<Bar>().name = "My First Bar";
    {
        Foo &f = obdb.allocate<Foo>();
        f.name = "My Second Foo";
        Bar &b = obdb.allocate<Bar>();
        b.name = "My Second Bar";
    }
    obdb.allocate<Foo>();
    obdb.allocate<Bar>();
    {
        cout << "Printing Foo Names\n";
        deque<Foo> &foos = obdb.getObjects<Foo>();
        for ( deque<Foo>::iterator i = foos.begin(); i!=foos.end(); ++i )
            cout << "   -> " << i->name << "\n";
    }
    {
        cout << "Printing Bar Names\n";
        deque<Bar> &bars = obdb.getObjects<Bar>();
        for ( deque<Bar>::iterator i = bars.begin(); i!=bars.end(); ++i )
            cout << "   -> " << i->name << "\n";
    }
}

When I run this program, I get this output:

Printing Foo Names
   -> My First Foo
   -> My Second Foo
   -> Generic Foo
Printing Bar Names
   -> My First Bar
   -> My Second Bar
   -> 

This shows that the individual objects are stored in containers specific to their own type. You'll notice that Foo and Bar are nothing special, just regular aggregates. (Foo would even be a POD if it weren't for its default constructor.)

======== EDIT ========

If you don't want to use RTTI, you need to get rid of the typeid and dynamic_cast.

Getting rid of the dynamic_cast is fairly simple --- you don't actually need it. You can use static_cast instead; you just can't check that the derived type is correct with the assert() anymore. (But if the type was wrong, it would be a bug anyway.)

The typeid is a bit trickier, since that is used to construct an identifier to differentiate between different concrete types. But you can use some template magic and static objects to replace the string (from type_info::name()) with a simple void const * pointer:

template<typename T>
struct TypeTag {
    static char const tag;
};
template<typename T>
char const TypeTag<T>::tag = '\0';

template<typename T>
void const *get_typemarker() {
    return &TypeTag<T>::tag;
}

Now we can use get_typemarker<T>() to return a void const * key into the map. We change the type of ObDB's key from string to void const * and replace typeid(T).name() with get_typemarker<T>(). I've tested it and it gives the same output in my test program as the RTTI-enabled version.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top