为什么 include Guard 不让循环 #include 起作用?
我有三个类:GameEvents
、Physics
和 GameObject
。我为每个人都有标题。
GameEvents
有一个Physics
和一系列GameObjects
。Physics
有一个GameObjects
列表。
注意循环依赖。我试图实现 GameObject
能够访问或拥有 Physics
对象。
如果我只是在 GameObject
中#include "Physics.h"
,我得到 我理解的错误C2111:'ClassXXX':'class'类型重新定义。 这是我认为包含防护会有所帮助的地方,因此我在我的 Physics.h
中添加了一个包含防护,因为这是我想要包含两次的标头。
这就是它的样子
#ifndef PHYSICS_H
#define PHYSICS_H
#include "GameObject.h"
#include <list>
class Physics
{
private:
double gravity;
list<GameObject*> objects;
list<GameObject*>::iterator i;
public:
Physics(void);
void ApplyPhysics(GameObject*);
void UpdatePhysics(int);
bool RectangleIntersect(SDL_Rect, SDL_Rect);
Vector2X CheckCollisions(Vector2X, GameObject*);
};
#endif // PHYSICS_H
但是如果我在我的 GameObject.h 中 #include "Physics.h"
现在像这样:
#include "Texture2D.h"
#include "Vector2X.h"
#include <SDL.h>
#include "Physics.h"
class GameObject
{
private:
SDL_Rect collisionBox;
public:
Texture2D texture;
Vector2X position;
double gravityForce;
int weight;
bool isOnGround;
GameObject(void);
GameObject(Texture2D, Vector2X, int);
void UpdateObject(int);
void Draw(SDL_Surface*);
void SetPosition(Vector2X);
SDL_Rect GetCollisionBox();
};
我遇到多个问题,但不明白为什么会出现这些问题重新出现。 如果我不#include "Physics.h"
我的代码运行得很好。
I have three classes: GameEvents
, Physics
and GameObject
. I have headers for each of them.
GameEvents
has onePhysics
and a list ofGameObjects
.Physics
has a list ofGameObjects
.
Note the circular dependency. I'm trying to achieve that GameObject
is able to access or own a Physics
object.
If I simply #include "Physics.h"
in GameObject
, I geterror C2111: 'ClassXXX' : 'class' type redifinition
which I understand.
This is where I thought include guards would help so I added an include guard to my Physics.h
since that's the header I want to include twice.
This is how it looks
#ifndef PHYSICS_H
#define PHYSICS_H
#include "GameObject.h"
#include <list>
class Physics
{
private:
double gravity;
list<GameObject*> objects;
list<GameObject*>::iterator i;
public:
Physics(void);
void ApplyPhysics(GameObject*);
void UpdatePhysics(int);
bool RectangleIntersect(SDL_Rect, SDL_Rect);
Vector2X CheckCollisions(Vector2X, GameObject*);
};
#endif // PHYSICS_H
But if I #include "Physics.h"
in my GameObject.h
now like this:
#include "Texture2D.h"
#include "Vector2X.h"
#include <SDL.h>
#include "Physics.h"
class GameObject
{
private:
SDL_Rect collisionBox;
public:
Texture2D texture;
Vector2X position;
double gravityForce;
int weight;
bool isOnGround;
GameObject(void);
GameObject(Texture2D, Vector2X, int);
void UpdateObject(int);
void Draw(SDL_Surface*);
void SetPosition(Vector2X);
SDL_Rect GetCollisionBox();
};
I get multiple issues that don't understand why they're showing up.
If I don't #include "Physics.h"
my code runs just fine.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
预处理器是一个程序,它接受您的程序,进行一些更改(例如包含文件(#include)、宏扩展(#define)以及基本上以
#
开头的所有内容)并给出“干净的” " 将结果发送给编译器。当预处理器看到
#include
时,它的工作方式如下:当您编写时:
some_file
的内容几乎从字面上复制粘贴到包含它的文件中。现在,如果您有:And:
And:
您将得到:
现在您可以看到
A
是如何重新定义的。当你编写守卫时,它们会变成这样:
现在让我们看看 main 中的
#include
如何扩展(这与前面的情况完全一样:复制粘贴)现在让我们跟随预处理器看看从中得出什么“真正的”代码。我会逐行:
评论。忽略!继续:
A_H
定义了吗?不!然后继续:好的,现在
A_H
已定义。继续:这不是预处理器的事情,所以就不用管它了。继续:
前面的
if
到这里就结束了。继续:评论。忽略!继续:
B_H
定义了吗?不!然后继续:好的,现在
B_H
已定义。继续:A_H
定义了吗?是的!然后忽略,直到对应的#endif
:忽略
忽略
前面的
if
到这里就结束了。继续:这不是预处理器的事情,所以就不用管它了。继续:
前面的
if
到这里就结束了。也就是说,在预处理器处理完文件后,编译器会看到以下内容:
正如您所看到的,任何可以在同一个文件中获得
#include
d 两次的内容,无论是直接还是间接都需要被守护。由于.h
文件总是很可能被包含两次,因此最好保护所有 .h 文件。PS 请注意,您还有循环
#include
。想象一下预处理器将Physics.h的代码复制粘贴到GameObject.h中,它会看到有一个#include“GameObject.h”
,这意味着将GameObject.h
复制到其自身中。当你复制时,你再次得到#include "Pysics.h"
并且你永远陷入了循环。编译器会阻止这种情况发生,但这意味着您的#include 已经完成了一半。在说如何解决这个问题之前,您应该了解另一件事。
如果您有:
那么编译器需要了解有关
b
的所有信息,最重要的是,它有哪些变量等,以便它知道应该放置多少字节来代替b
> 在A
中。但是,如果您有:
那么编译器实际上不需要了解有关
B
的任何信息(因为无论类型如何,指针都具有相同的大小)。关于B
它唯一需要知道的是它存在!因此,您执行称为“前向声明”的操作:
这与您在头文件中执行的许多其他操作非常相似,例如:
The preprocessor is a program that takes your program, makes some changes (for example include files (#include), macro expansion (#define), and basically everything that starts with
#
) and gives the "clean" result to the compiler.The preprocessor works like this when it sees
#include
:When you write:
The contents of
some_file
almost literally get copy pasted into the file including it. Now if you have:And:
And:
You get:
Now you can see how
A
is redefined.When you write guards, they become like this:
So now let's look at how
#include
s in main would be expanded (this is exactly, like the previous case: copy-paste)Now let's follow the preprocessor and see what "real" code comes out of this. I will go line by line:
Comment. Ignore! Continue:
Is
A_H
defined? No! Then continue:Ok now
A_H
is defined. Continue:This is not something for preprocessor, so just leave it be. Continue:
The previous
if
finished here. Continue:Comment. Ignore! Continue:
Is
B_H
defined? No! Then continue:Ok now
B_H
is defined. Continue:Is
A_H
defined? YES! Then ignore until corresponding#endif
:Ignore
Ignore
The previous
if
finished here. Continue:This is not something for preprocessor, so just leave it be. Continue:
The previous
if
finished here.That is, after the preprocessor is done with the file, this is what the compiler sees:
So as you can see, anything that can get
#include
d in the same file twice, whether directly or indirectly needs to be guarded. Since.h
files are always very likely to be included twice, it is good if you guard ALL your .h files.P.S. Note that you also have circular
#include
s. Imagine the preprocessor copy-pasting the code of Physics.h into GameObject.h which sees there is an#include "GameObject.h"
which means copyGameObject.h
into itself. When you copy, you again get#include "Pysics.h"
and you are stuck in a loop forever. Compilers prevent that, but that means your#include
s are half-done.Before saying how to fix this, you should know another thing.
If you have:
Then the compiler needs to know everything about
b
, most importantly, what variables it has etc so that it would know how many bytes it should put in place ofb
inA
.However, if you have:
Then the compiler doesn't really need to know anything about
B
(since pointers, regardless of the type have the same size). The only thing it needs to know aboutB
is that it exists!So you do something called "forward declaration":
This is very similar to many other things you do in header files such as:
您在这里有循环引用:
Physics.h
包含GameObject.h
,其中包含Physics.h
。您的类Physics
使用GameObject*
(指针)类型,因此您无需在Physics.h< 中包含
GameObject.h
/code> 但只需使用前向声明 - 而不是put
此外,在每个头文件中放置防护。
You have circular references here:
Physics.h
includesGameObject.h
which includesPhysics.h
. Your classPhysics
usesGameObject*
(pointer) type so you don't need to includeGameObject.h
inPhysics.h
but just use forward declaration - instead ofput
Furthermore, put guards in each header file.
问题是您的
GameObject.h
没有防护,因此当您在Physics.h
中#include "GameObject.h"
时,它会得到当GameObject.h
包含Physics.h
时包含。The issue is that your
GameObject.h
does not have guards, so when you#include "GameObject.h"
inPhysics.h
it gets included whenGameObject.h
includesPhysics.h
.在所有
*.h
或*.hh
头文件中添加包含防护(除非您有特定原因不这样做)。要了解发生了什么,请尝试获取源代码的预处理形式。对于 GCC,它类似于
g++ -Wall -C -E yourcode.cc > yourcode.i
(我不知道微软编译器是如何做到这一点的)。您还可以询问包含哪些文件,GCC 为g++ -Wall -H -c yourcode.cc
Add include guards in all your
*.h
or*.hh
header files (unless you have specific reasons to not do that).To understand what is happening, try to get the preprocessed form of your source code. With GCC, it is something like
g++ -Wall -C -E yourcode.cc > yourcode.i
(I have no idea on how Microsoft compilers do that). You can also ask which files are included, with GCC asg++ -Wall -H -c yourcode.cc
首先你也需要在游戏对象上包含守卫,但这不是真正的问题
如果其他东西首先包含物理.h,物理.h包含游戏对象.h,你会得到这样的东西:
并且#include物理.h被丢弃,因为包含守卫,最终您会在声明物理之前声明游戏对象。
但如果您希望 GameObject 有一个指向物理的指针,那就是一个问题,因为对于 htat 物理必须首先声明。
要解决这个循环,您可以前向声明一个类,但前提是您仅在下面的声明中将其用作指针或引用,即:
Firstly you need include guards on gameobject too, but that's not the real problem here
If something else includes physics.h first, physics.h includes gameobject.h, you get something like this:
and the #include physics.h gets discarded because of the include guards, and you end up with a declaration of GameObject before the declaration of Physics.
But that's a problem if you want GameObject to have a pointer to a Physics, because for htat physics would have to be declared first.
To resolve the cycle, you can forward-declare a class instead, but only if you are just using it as a pointer or a reference in the declaration following, i.e.:
在所有头文件中使用包含防护。由于您使用的是 Visual Studio,因此您可以使用
#pragma Once
作为所有标头中的第一个预处理器定义。不过,我建议使用经典方法:
第二次阅读 转发声明并应用它。
Use include guards in ALL your header files. Since you are using Visual Studio you could use the
#pragma once
as the first preprocessor definition in all your headers.However I suggest to use the classical approach:
Second read about forward declaration and apply it.
标头保护的目标是避免多次包含同一文件。
但目前C++中使用的头保护还可以改进。当前的守卫是:
我的新守卫建议是:
这解决了当 AAA 类需要 BBB 类声明,而 BBB 类需要 AAA 类声明时出现的恼人问题,通常是因为从一个类到另一个类存在交叉指针:
我希望将其包含在自动从模板生成代码的 IDE 中。
The goal of a header guard is to avoid including the same file many times.
But the header guard that is currently used in C ++ can be improved. The current guard is:
My new guard proposal is:
This resolves the annoying problem that occurs when the AAA class needs the BBB class declaration, while the BBB class needs the AAA class declaration, typically because there are crossed pointers from one class to the other:
I would love for this to be included in the IDEs that automatically generate code from templates.
" #pragma Once " ::: 与标头保护具有相同的目的,并且具有更短且不易出错的额外好处。
许多编译器支持使用 #pragma 指令的更简单的替代形式的标头保护:
“ #pragma 一次 ”
// 你的代码在这里
但是,#pragma Once 并不是 C++ 语言的官方部分,并且并非所有编译器都支持它(尽管大多数现代编译器都支持)。
出于兼容性目的,人们建议坚持使用传统的标头防护装置。它们不需要做更多的工作,并且保证所有兼容的编译器都支持它们。
" #pragma once " ::: serves the same purpose as header guards, and has the added benefit of being shorter and less error-prone.
Many compilers support a simpler, alternate form of header guards using the #pragma directive:
" #pragma once "
// your code here
However, #pragma once is not an official part of the C++ language, and not all compilers support it (although most modern compilers do).
For compatibility purposes, people recommend sticking to traditional header guards. They aren’t much more work and they’re guaranteed to be supported on all compliant compilers.