在std::map中存储结构体实例

5

我正在尝试将一些结构体映射到其他实例,就像这样:

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

然后我像这样使用它:
struct UnitInfos {
    int owner_id;
    int health;
    float x, y;
};

class LogicComponent : public Component<UnitInfos> {};

问题在于当它稍后检索数据时,像这样:
comp.instance[id];

我使用默认值初始化了一个全新的对象。

这段代码有什么本质的问题,还是我遗漏了一些与问题有关的信息?


根据 @aaa 的建议,我将代码更改为:

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

但是当我访问它时

UnitInfos &info = logic_c.instances[id];

信息.x的值仍然是0。有什么指针吗?


问题在于我如何在另一个类中存储对LogicComponent的引用。使用LogicComponent logic_c;而不是LogicComponent& logic_c;。现在它可以工作,但是我正在将指针存储在地图中(而不是@aaa的建议)。这是个坏主意吗?


1
这段代码本质上存在问题:不要动态创建 T 。没有理由这样做,而且现在的写法会导致对象泄漏。至于你看到的问题,你需要发布 EntityID 的小于运算符实现以及你如何调用地图上的 operator[] (也就是你如何创建传递给 operator[] 的 ID )。 - James McNellis
EntityID 只是被定义为 typedef unsigned long EntityID; - sharvey
3个回答

4
可能是这样的。
T add(EntityID id) {
    T* t = new T();
    instances[id] = *t;
    return *t;  // return value and map instance are not the same anymore
};  

应该是

T& add(EntityID id) {
    instances[id] = T();
    return instances[id];
};  

我尝试更改代码,但它仍然给我同样的问题。我应该像这样检索实例吗:UnitInfos info = logic_c.instances[id]; - sharvey
@sharvey 不是的,UnitInfos info 是一个新实例,UnitInfos &info 是对现有实例的引用。 如果您来自Java,您可能需要阅读此内容:http://www.parashift.com/c++-faq-lite/references.html - Anycorn
我来自于Java的背景。奇怪的是,当我在更改后立即访问它时,值是正确的;但是当我稍后再次获取它时,x的值仍然为0。我会更新问题。谢谢。 - sharvey
十二年后,C++ FAQ已经迁移至:https://isocpp.org/wiki/faq/references - Daniel Griscom

4

请明确您想在LogicComponent上执行的操作。假设您正在尝试实现类似以下内容的操作:

步骤1:向映射表中添加新条目:

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

步骤2:初始化信息:
info.x = 10.0;
info.y = 11.0
// etc

第三步:再次获取信息对象:
UnitInfos info2 = comp.instances[id]; // this is uninitialized.

接下来需要添加一些代码注释:

comp.add返回的info对象是你添加到地图中的对象的一个副本。通过修改它,你并没有修改地图中的对象。

最简单的解决方法是创建一个指向该对象的指针映射,而不是直接使用该对象。

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

此外,请使用访问器方法获取映射值,而不是将映射对象公开为公共对象。将实例变量设置为受保护状态,并添加一个公共的 get() 方法。
编辑:此代码对我来说很好用:
#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;
}

你所描述的正是我想要实现的。我尝试了按照你所描述的方式进行切换,但在最后一行后调用 info->x 时,出现了 EXC_BAD_ACCESS 错误,并且 info 指向 0x0。 - sharvey
我同意,这段代码是有效的。但当我尝试从代码的另一个部分(传递给glutDisplayFuncglutIdleFunc的渲染函数)访问它时,指针仍然指向0x0。 - sharvey
你是否正在访问同一个 LogicComp 对象? - carlsborg
不,我不是。我有一个类成员存储了定义为LogicComponent logic_c;的LogicComponent。将其更改为LogicComponent&logic_c;就解决了!非常感谢。 - sharvey

2

听起来你定义了你的索引操作符:

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

或者类似那样。

这可能会产生意料之外的影响,即它会自动将T的默认构造实例插入到映射中,然后返回它“对于不存在的条目”。在std::map中完成此操作,因此可以使用自然赋值语法,例如instances[10] = t;

关键点在于constness。将其定义为与上述完全相同,只是通过返回值并带有const属性:

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

这样,当您尝试检索不存在的键时,将会得到一个异常。更好的方法是,只需像下面这样typedef它,然后就完成了:

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

其他人已经提到,您不需要动态分配对象 - 您无论如何都会将副本存储在容器中 - 并且当您这样做时会泄漏内存。


谢谢,我现在明白为什么c++faq-lite说你在c++中大部分时间可能不需要指针了。没想到它也适用于这样的情况。我通过实例的子字段访问实例,所以我认为[]运算符没有在组件模板上调用。当然,我可能是错的。我一定会像你建议的那样实现operator[],因为它更好用。谢谢。 - sharvey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接