views:

125

answers:

3

I'm trying to map some structs to some other instances, like this:

template <typename T>
class Component {
public:

    typedef std::map<EntityID, T> instances_map;

    instances_map instances;

    Component() {};

    T add(EntityID id) {
        T* t = new T();
        instances[id] = *t;
        return *t;
    };  
};

Then I use it like this:

struct UnitInfos {
    int owner_id;
    int health;
    float x, y;
};

class LogicComponent : public Component<UnitInfos> {};

The problem is that when it later retrieve data later on, like this:

comp.instance[id];

I get a breand new object with properties initialized at default values.

Is there something inherently wrong with this piece of code, or am I leaving out information about the problem?


As per @aaa suggestion, i change the code to

typedef std::map<EntityID, T> instances_map;
instances_map instances;
T& add(EntityID id) {
    instances[id] = T();
    return instances[id];
};

but when I access it

UnitInfos &info = logic_c.instances[id];

the value of info.x is still 0. Any pointers?


The problem was how I stored the reference to LogicComponent in another class. using LogicComponent logic_c; instead of LogicComponent& logic_c;. It now works, but I'm storing pointers in the map (instead of @aaa's suggestion). Is this a bad idea?

+4  A: 

might be that

T add(EntityID id) {
    T* t = new T();
    instances[id] = *t;
    return *t;  // return value and map instance are not the same anymore
};  

should be

T& add(EntityID id) {
    instances[id] = T();
    return instances[id];
};  
aaa
I tried changing the code, but it still gives me the same problem. Should I be retrieving the instance like this: `UnitInfos info = logic_c.instances[id];` ?
sharvey
aaa
@aaa I do come from a java-ish background. The weird thing is that when I access it right after changing it, the value is correct; but when I get it again later on, the value of x is still 0. Ill update the question. Thanks.
sharvey
+2  A: 

It sounds like you defined your indexing operator as:

template <typename T>
T& Component::operator[]( EntityID id )
{
    return instances[id];
}

Or something like that.

The likely unexpected effect of this is that it will automatically insert default-constructed instance of T into the map and then return it for non-exising entries. This is done in std::map so natural assignment syntax like instances[10] = t; works.

The key point here is constness. Define it exactly as above except returning by value and with a const attribute:

template <typename T>
T Component::operator[]( EntityID id ) const
{
    return instances[id];
}

This way you will get an exception when you try retrieving by non-existing key. Better yet, just typedef it like bellow and be done with it:

typedef std::map<EntityID,UnitInfos> EntityUnitMap;

Others already mentioned that you don't need to dynamically allocate an object - you store a copy in the container anyway - and that you leak memory when you do that.

Nikolai N Fetissov
Thanks, I now understand why the c++faq-lite says that you probably don't need pointer most of the time in c++. Didn't think it would also apply to something like this. I am accessing the instance throught the instances subfield, so I don't the the [] operator is called on the Component template. I might be wrong, of course.I will absolutely implement the operator[] like you suggested, because it's a lot nicer. thanks.
sharvey
+3  A: 

Clarify the operations you want to perform on LogicComponent. Assuming you are trying to achieve something like this:

Step 1: Add a new entry to the map:

LogicComponent comp; 
EntityID id = 99;
UnitInfos info = comp.add(id);

Step 2: Initialize the info:

info.x = 10.0;
info.y = 11.0
// etc

Step 3: Get the info object again:

UnitInfos info2 = comp.instances[id]; // this is uninitialized.

Then, a few code comments are in order:

The info object returned by comp.add is a COPY of the object you added to the map. By modifying it, you are not modifying what is in the map.

The simplest fix is to create a map of pointers to the object instead of the object itself.

typedef std::map<EntityID, T*> pinstances_map;

T * add(EntityID id) {
    T* t = new T();
    instances[id] = t;
    return t;
};  

// initialize as 
UnitInfo *info = comp.add(id);
info->x = 10.0;
info->y = 11.0;

// retrieve as 
UnitInfos *info = comp.instances[id];

Also, do use an accessor method to get the mapped value, instead of exposing the map object as public. Make the instances variable protected, and add a public get() method.

Edit: This code works fine for me:

#include <map>
#include <iostream>
using namespace std;

template<typename T>
class Component
{
public:
        typedef map<long, T*> pinstances_map;
        pinstances_map instances;

        T * add(long id)
        {
                T *t = new T();
                instances[id] = t;
                return t;
        }
};

struct UnitInfo 
{
        float x, y;
};

class LogicComponent: public Component<UnitInfo> {};

int main()
{
        LogicComponent comp;
        UnitInfo *info = comp.add(99);
        info->x = 10.0;
        info->y = 11.0;

        UnitInfo *info2 = comp.instances[99];
        cout << info2->x << " " << info2->y;

        return 0;
}
tholomew
What you described is exactly what I trying to achieve. Switching to what you have describded is somethink I tried, but when calling info->x after your last line, i get a EXC_BAD_ACCESS, with info pointing to 0x0.
sharvey
I agree, this code works. When I try to access it from another part ofthe code (the render function passed to `glutDisplayFunc` and `glutIdleFunct`, the pointers still points to 0x0.
sharvey
Are you accessing the same LogicComp object?
tholomew
No I wasn't. I had a class member storing the LogicComponent defined as `LogicComponent logic_c;`. Changing that to `LogicComponent` fixed it! Thanks a lot.
sharvey