1

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

4
  • Aren't you reinventing the variant? You can either use boost::variant or QVariant as T and avoid template, or at a look at how they are implemented if you want your own object Commented Jan 21, 2014 at 21:11
  • You can wrap the array (e.g. vector<T>) as a data member into a class template that derives from a base class. E.g. struct base { /* pure virtual functions */ }; template<class T> struct wrapper : base { vector<T> arr; /*virtual functions*/ }; And then store a map<int, wrapper> via some type->int mapping. Commented Jan 21, 2014 at 21:11
  • I don't want any virtual functions, as T is just a structure in all cases with no methods. Im just storing sets of data. You can think of it as a set of arrays, where each array stores a single type. If I use boost::variant I believe I have to fetch the appropriate type for element in the array as I iterate which will slow things down.. Commented Jan 21, 2014 at 22:31
  • What I was suggesting is to wrap the array in a class with virtual functions. Inside this class you can access the array w/o any overhead. Something like this: coliru.stacked-crooked.com/a/45546ec43180306e Commented Jan 21, 2014 at 23:48

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

10 Comments

Yes this is exactly what I'm after! Thank you I can work with this. Although I don't have RTTI enabled.. How would it work without typeid() and dynamic_cast?
@Mash, I've added some new material to the bottom of the answer that should help you with what you want.
@Mash I'm pretty sure typeid(T) is not RTTI. It's not a run-time feature that checks the type of some object, but provides a type information object of a type. The dynamic_cast is simply not necessary, assuming unique type names.
"The dynamic_cast<> operation and typeid operator in C++ are part of RTTI." And also I wasn't aware of this! "When applied to an expression of polymorphic type, evaluation of a typeid expression may involve runtime overhead (a virtual table lookup), otherwise typeid expression is resolved at compile time." I normally work in a games industry where RTTI is disabled, so always like alternative solutions that don't rely on those features. Thanks so much for this answer! Im not sure I entirely understand the point of collection base and collection<T> yet.
@Mash, the reason for CollectionBase and Collection<T> is so the ObjectDatabase can use a map to handle different containers for different types. CollectionBase serves as a common base class for all the various containers so they can be stored in a single map (as pointers). A Collection<T> holds a specific container for a set of objects, and it inherits from CollectionBase so that it can be held along with other Collection<T>s of other types. To access each specific container, you must downcast. (CollectionBase also provides the needed virtual destructor for clean-up.)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.