如何让 class 包含指向自身的指针列表?

How to have a class contain a list of pointers to itself?

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

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

中创建的,则游戏 运行s

有 3 种可能的解决方案:我将此列表放在 Globals.h 中,我将单例模式应用于游戏对象 class,或者我将此列表放在 GameObject.h 中,但在游戏对象 class。所有解决方案都会导致错误。

我认为解决方案 1 会导致循环依赖,因为 Globals.h 定义了 GameObject class 所需的枚举值,但该列表由 GameObject 指针组成。列表中的第一个错误显示 "GameObject is undeclared identifier",并出现在 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 class 中有列表,我可以编译,但是一旦我尝试将其自身推入列表,链接器就会抛出错误。它说 "unresolved external symbol"。如果 objects 本身也是一个指针,也没有变化。

//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种方法更好的方法吗?

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

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

主要是,我得到一个未解决的外部问题。

修改第一种情况即可: 在创建 GameObject 的指针的 list 之前声明 class 原型

//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;时,它会自动生成默认构造函数,这与我们的定义冲突早先提供给 class。

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

您 forward-declare 在 class 之前要使用 class 名称来创建指向它的指针或引用。您不需要完整的 class 定义。您还应该只在 header 文件中声明全局变量,而不是定义它们。如果您定义它们并在多个源文件中包含 header,您最终会遇到多个定义链接器错误。当然,您确实需要在某些源文件中定义变量(一次)。

//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

1 和 2 是不错的想法,只是理解上的一些小差距:

1 - 在全局变量中声明

循环依赖通过前向声明解决。在您访问其成员或需要知道它有多大(通常实例化一个对象)之前,您不需要完整的 class 定义。

您将遇到的下一个问题是,这会导致在每个包含 Globals.h 的源文件中创建一个单独的 objects 列表。您可以通过在声明中使用 extern 关键字来避免这种情况(告诉编译器暂时不要为其分配 space )并在没有 extern 的情况下在正好 1 个 cpp 文件中定义它(内存是分配在那里)。然而,这更像是一种 C 风格的做法:在现代 C++ 中,使用静态 class 成员(如 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 - 静态 class 成员

很像 extern 关键字,当你声明一个静态 class 成员时,它实际上并没有在程序中分配 space 供链接器查找,所以它抛出未解决的符号错误。您需要以 1 cpp 为单位定义存储 space。

此外,通常的做法是像这样 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 的单独副本。