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

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

本文关键字:指针 列表 包含指      更新时间:2023-10-16

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

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

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

我相信解决方案 1 会导致循环依赖关系,因为 Globals.h 定义了游戏对象类所需的枚举值,但该列表由游戏对象指针组成。列表中的第一个错误显示"游戏对象是未声明的标识符",并在全局变量中弹出。

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

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

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

总的来说,我得到了一个未解决的外部。

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

1 - 在全局变量中声明

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

您将遇到的下一个问题是,这会导致在包含 Globals.h 的每个源文件中创建一个单独的objects列表。您可以通过在声明中使用 extern 关键字(告诉编译器暂时不要为其分配空间)并在 1 个 cpp 文件中定义它而不带extern(内存在那里分配)来避免这种情况。但是,这更像是一种 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 副本。

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

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

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

//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构造函数,或者提供默认构造函数的实现,这已经在代码中完成。