我正在用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);
在主要方面,我得到一个未解决的外部。
答案 0 :(得分:1)
1和2是合理的想法,只是理解中的一些小差距:
使用前向声明解决循环依赖关系。在访问其成员或需要知道其大小(通常是实例化对象)之前,您不需要完整的类定义。
您将遇到的下一个问题是,这会导致在包含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.
与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
不要这样做。
包含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