如何让一个类包含一个指向自己的指针列表?

时间:2015-01-29 07:16:04

标签: c++ list linker-errors circular-dependency

我正在用C ++制作游戏。在这个游戏中,有一个GameObject指针列表。 GameObject是可以在世界中与之交互的每个对象的基类。主要的工作是充当裁判,因此它会告知每个对象有关碰撞,渲染,删除它们等等。

玩家的角色有能力。一些技能可能会产生射弹。每个射弹都不同,但都来自GameObject。必须将它们添加到列表中,以便主要可以看到它们。我希望GameObject指针列表是全局的,这样对象可以将自己推送到列表中,但main仍然可以看到并更新它们。目前,如果列表是在main中创建的,游戏就会运行。

有三种可能的解决方案:我把这个列表放在Globals.h中,我将单例模式应用于GameObject类,或者我把这个列表放在GameObject.h中,但是在GameObject类之外。所有解决方案都会导致错误。

我认为解决方案1会导致循环依赖,因为Globals.h定义了GameObject类所需的枚举值,但该列表由GameObject指针组成。列表中的第一个错误是“GameObject是未声明的标识符”,并在Globals中出现。

//Globals.h
#include "GameObject.h"
#include <list>
std::list<GameObject *> objects;
enum Id{ENEMY, PLAYER, ROCK};

//GameObject.h
#pragma once
#include "Globals.h"
class GameObject{
    public:
        GameObject();
        ~GameObject();
        Id id;
};

对于解决方案2,我可以编译,如果我在GameObject类中有列表,但是一旦我尝试将自己推入列表,链接器就会抛出错误。它说“未解决的外部符号”。如果对象本身也是一个指针,则无需更改。

//GameObject.h
#pragma once
#include "Globals.h"
#include <list>
class GameObject{
    public:
        static std::list <GameObject *> objects;
        GameObject();
        ~GameObject();
        void init(){ objects.push_back(this); }
        Id id;
};

对于最终解决方案,链接器告诉我main.obj已经定义了列表对象符号。找到一个或多个乘法符号。

//GameObject.h
#pragma once
#include "Globals.h"
#include <list>
class GameObject{
    public:
        GameObject();
        ~GameObject();
        Id id;
};
std::list <GameObject *> objects;

我已经完全没有想法了,我真的需要这个列表才能访问。解决方案1是最优选的,然后是2然后3.如何使此列表全局可访问?可能吗?有没有比我试过的3种方式更好的方法?

更新:解决方案1将编译,如果我使用前向声明和extern关键字,但当我尝试

GameObject *test = new GameObject
objects.push_back(test);

在主要方面,我得到一个未解决的外部。

3 个答案:

答案 0 :(得分:1)

1和2是合理的想法,只是理解中的一些小差距:

1 - 在全局

中声明

使用前向声明解决循环依赖关系。在访问其成员或需要知道其大小(通常是实例化对象)之前,您不需要完整的类定义。

您将遇到的下一个问题是,这会导致在包含Globals.h的每个源文件中创建单独的objects列表。您可以通过在声明中使用extern关键字来避免这种情况(告诉编译器不要为它分配空间)并在没有extern的情况下将其定义为1 cpp文件(在那里分配内存)。但是,这更像是一种C风格的实践:在现代C ++中,使用静态类成员是更标准的做法(如2)。

//Globals.h
#include "GameObject.h"
#include <list>
class GameObject; // Forward declaration
extern std::list<GameObject *> objects; // Declare, don't define
enum Id{ENEMY, PLAYER, ROCK};

//GameObject.h
#pragma once
#include "Globals.h"
class GameObject{
    public:
        GameObject();
        ~GameObject();
        Id id;
};

//GameObject.cpp
#include "GameObject.h"
std::list<GameObject *> objects; // The list lives here.

2 - 静态类成员

extern关键字非常相似,当您声明静态类成员时,它实际上并不在程序中为链接器分配空间,因此它会抛出未解析的符号错误。您需要精确定义1 cpp。

中的存储空间

此外,通常的做法是创建像private这样的全局可访问成员,但创建一个public访问者函数。这是一个容易进入的习惯,有时会为你省去一些麻烦。

//GameObject.h
#pragma once
#include "Globals.h"
#include <list>
class GameObject{
    public:
        GameObject();
        ~GameObject();
        void init(){ objects.push_back(this); }
        Id id;
        // Accessor function
        static std::list <GameObject *> & GetAllObjects() { return objects; }
    private:
        static std::list <GameObject *> objects; // Declaration only
};

//GameObject.cpp
#include "GameObject.h"
std::list <GameObject *> GameObject::objects; // Definition

3 - 多份副本

不要这样做。

包含GameObject.h的每个文件最终都会使用objects的单独副本。

答案 1 :(得分:0)

你可以通过修改第一种情况来做到这一点: 在创建list指针GameObject之前声明类原型

//Globals.h
class GameObject; //Defined the class prototype first before including
                  //"GameObject.h"
#include "GameObject.h"
#include <list>
std::list<GameObject *> objects;
enum Id{ENEMY, PLAYER, ROCK};

//GameObject.h
#pragma once
#include "Globals.h"
class GameObject{ //Class body
    public:
        GameObject() {} 
        ~GameObject() {}
        Id id;
};

我定义了GameObject::GameObject,因为当编译器遇到行class GameObject;时,它会自动生成默认构造函数,冲突与我们之前提供给类的定义

因此,我们可以删除 GameObject构造函数,或提供默认构造函数的实现,这已在代码中完成。

答案 2 :(得分:0)

在您想要使用类名来创建指针或对它的引用之前,您可以向前声明该类。您不需要完整的类定义。您还应该只在头文件中声明全局变量,而不是定义它们。如果您定义它们并在多个源文件中包含标头,则最终会出现多个定义链接器错误。当然,您需要在某个源文件中定义变量(一次)。

//Globals.h
#include <list>
class GameObject;  // forward declaration of GameObject
extern std::list<GameObject *> objects; // declaration of objects
enum Id{ENEMY, PLAYER, ROCK};

//Globals.cpp
#include "Globals.h"
std::list<GameObject *> objects;  // definition of objects