【问题标题】:How to have a class contain a list of pointers to itself?如何让一个类包含指向自身的指针列表?
【发布时间】:2015-01-29 07:16:04
【问题描述】:

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

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

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

我相信解决方案 1 会导致循环依赖,因为 Globals.h 定义了 GameObject 类所需的枚举值,但列表由 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 类中有列表,我可以编译,但是一旦我尝试将自己推送到列表中,链接器就会抛出错误。它说“未解决的外部符号”。如果对象本身也是一个指针,也不会改变。

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

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

【问题讨论】:

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


【解决方案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 的单独副本。

【讨论】:

  • 谢谢,效果很好。我注释掉了大部分代码以使这个问题尽可能简单,所以我将慢慢将代码滴回。手指交叉!
  • 当 GameObject 类被插入到一个可复制/可移动的列表中时,所有这些指针都会“噗”的一声:)
【解决方案2】:

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

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

【讨论】:

  • 如果 Globals.h 包含在多个 cpp 文件中而没有使用 extern,我认为这不会起作用。
【解决方案3】:

您可以通过修改第一种情况来做到这一点: 在创建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 构造函数,或者提供默认构造函数的实现,这已在代码中完成。

【讨论】:

  • 与 extern 关键字一起使用,但是我不能在 main 中使用对象列表。感谢您的尝试,您让我走上了正确的道路。
  • @user3263864 我已经编辑了我的代码,这应该可以工作。
猜你喜欢
  • 1970-01-01
  • 2011-04-28
  • 1970-01-01
  • 1970-01-01
  • 2018-05-30
  • 1970-01-01
  • 2015-04-23
  • 2020-07-02
  • 1970-01-01
相关资源
最近更新 更多