为什么 include Guard 不让循环 #include 起作用?

发布于 2024-12-13 14:11:38 字数 1619 浏览 3 评论 0原文

我有三个类:GameEventsPhysicsGameObject。我为每个人都有标题。

  • 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 one Physics and a list of GameObjects.
  • Physics has a list of GameObjects.

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 get
error 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

倒带 2024-12-20 14:11:38

预处理器是一个程序,它接受您的程序,进行一些更改(例如包含文件(#include)、宏扩展(#define)以及基本上以 # 开头的所有内容)并给出“干净的” " 将结果发送给编译器。

当预处理器看到 #include 时,它的工作方式如下:

当您编写时:

#include "some_file"

some_file 的内容几乎从字面上复制粘贴到包含它的文件中。现在,如果您有:

a.h:
class A { int a; };

And:

b.h:
#include "a.h"
class B { int b; };

And:

main.cpp:
#include "a.h"
#include "b.h"

您将得到:

main.cpp:
class A { int a; };  // From #include "a.h"
class A { int a; };  // From #include "b.h"
class B { int b; };  // From #include "b.h"

现在您可以看到 A 是如何重新定义的。

当你编写守卫时,它们会变成这样:

a.h:
#ifndef A_H
#define A_H
class A { int a; };
#endif

b.h:
#ifndef B_H
#define B_H
#include "a.h"
class B { int b; };
#endif

现在让我们看看 main 中的 #include 如何扩展(这与前面的情况完全一样:复制粘贴)

main.cpp:
// From #include "a.h"
#ifndef A_H
#define A_H
class A { int a; };
#endif
// From #include "b.h"
#ifndef B_H
#define B_H
#ifndef A_H          // From
#define A_H          // #include "a.h"
class A { int a; };  // inside
#endif               // "b.h"
class B { int b; };
#endif

现在让我们跟随预处理器看看从中得出什么“真正的”代码。我会逐行:

// From #include "a.h"

评论。忽略!继续:

#ifndef A_H

A_H 定义了吗?不!然后继续:

#define A_H

好的,现在 A_H 已定义。继续:

class A { int a; };

这不是预处理器的事情,所以就不用管它了。继续:

#endif

前面的if到这里就结束了。继续:

// From #include "b.h"

评论。忽略!继续:

#ifndef B_H

B_H 定义了吗?不!然后继续:

#define B_H

好的,现在 B_H 已定义。继续:

#ifndef A_H          // From

A_H 定义了吗?是的!然后忽略,直到对应的#endif

#define A_H          // #include "a.h"

忽略

class A { int a; };  // inside

忽略

#endif               // "b.h"

前面的if到这里就结束了。继续:

class B { int b; };

这不是预处理器的事情,所以就不用管它了。继续:

#endif

前面的if到这里就结束了。

也就是说,在预处理器处理完文件后,编译器会看到以下内容:

main.cpp
class A { int a; };
class B { int b; };

正如您所看到的,任何可以在同一个文件中获得 #included 两次的内容,无论是直接还是间接都需要被守护。由于 .h 文件总是很可能被包含两次,因此最好保护所有 .h 文件。

PS 请注意,您还有循环#include。想象一下预处理器将Physics.h的代码复制粘贴到GameObject.h中,它会看到有一个#include“GameObject.h”,这意味着将GameObject.h复制到其自身中。当你复制时,你再次得到#include "Pysics.h"并且你永远陷入了循环。编译器会阻止这种情况发生,但这意味着您的#include 已经完成了一半。

在说如何解决这个问题之前,您应该了解另一件事。

如果您有:

#include "b.h"

class A
{
    B b;
};

那么编译器需要了解有关 b 的所有信息,最重要的是,它有哪些变量等,以便它知道应该放置多少字节来代替 b > 在 A 中。

但是,如果您有:

class A
{
    B *b;
};

那么编译器实际上不需要了解有关 B 的任何信息(因为无论类型如何,指针都具有相同的大小)。关于 B 它唯一需要知道的是它存在!

因此,您执行称为“前向声明”的操作:

class B;  // This line just says B exists

class A
{
    B *b;
};

这与您在头文件中执行的许多其他操作非常相似,例如:

int function(int x);  // This is forward declaration

class A
{
public:
    void do_something(); // This is forward declaration
}

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:

#include "some_file"

The contents of some_file almost literally get copy pasted into the file including it. Now if you have:

a.h:
class A { int a; };

And:

b.h:
#include "a.h"
class B { int b; };

And:

main.cpp:
#include "a.h"
#include "b.h"

You get:

main.cpp:
class A { int a; };  // From #include "a.h"
class A { int a; };  // From #include "b.h"
class B { int b; };  // From #include "b.h"

Now you can see how A is redefined.

When you write guards, they become like this:

a.h:
#ifndef A_H
#define A_H
class A { int a; };
#endif

b.h:
#ifndef B_H
#define B_H
#include "a.h"
class B { int b; };
#endif

So now let's look at how #includes in main would be expanded (this is exactly, like the previous case: copy-paste)

main.cpp:
// From #include "a.h"
#ifndef A_H
#define A_H
class A { int a; };
#endif
// From #include "b.h"
#ifndef B_H
#define B_H
#ifndef A_H          // From
#define A_H          // #include "a.h"
class A { int a; };  // inside
#endif               // "b.h"
class B { int b; };
#endif

Now let's follow the preprocessor and see what "real" code comes out of this. I will go line by line:

// From #include "a.h"

Comment. Ignore! Continue:

#ifndef A_H

Is A_H defined? No! Then continue:

#define A_H

Ok now A_H is defined. Continue:

class A { int a; };

This is not something for preprocessor, so just leave it be. Continue:

#endif

The previous if finished here. Continue:

// From #include "b.h"

Comment. Ignore! Continue:

#ifndef B_H

Is B_H defined? No! Then continue:

#define B_H

Ok now B_H is defined. Continue:

#ifndef A_H          // From

Is A_H defined? YES! Then ignore until corresponding #endif:

#define A_H          // #include "a.h"

Ignore

class A { int a; };  // inside

Ignore

#endif               // "b.h"

The previous if finished here. Continue:

class B { int b; };

This is not something for preprocessor, so just leave it be. Continue:

#endif

The previous if finished here.

That is, after the preprocessor is done with the file, this is what the compiler sees:

main.cpp
class A { int a; };
class B { int b; };

So as you can see, anything that can get #included 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 #includes. Imagine the preprocessor copy-pasting the code of Physics.h into GameObject.h which sees there is an #include "GameObject.h" which means copy GameObject.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 #includes are half-done.

Before saying how to fix this, you should know another thing.

If you have:

#include "b.h"

class A
{
    B b;
};

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 of b in A.

However, if you have:

class A
{
    B *b;
};

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 about B is that it exists!

So you do something called "forward declaration":

class B;  // This line just says B exists

class A
{
    B *b;
};

This is very similar to many other things you do in header files such as:

int function(int x);  // This is forward declaration

class A
{
public:
    void do_something(); // This is forward declaration
}
森罗 2024-12-20 14:11:38

您在这里有循环引用:Physics.h 包含 GameObject.h,其中包含 Physics.h。您的类 Physics 使用 GameObject*(指针)类型,因此您无需在 Physics.h< 中包含 GameObject.h /code> 但只需使用前向声明 - 而不是

#include "GameObject.h" 

put

class GameObject;   

此外,在每个头文件中放置防护。

You have circular references here: Physics.h includes GameObject.h which includes Physics.h. Your class Physics uses GameObject* (pointer) type so you don't need to include GameObject.h in Physics.h but just use forward declaration - instead of

#include "GameObject.h" 

put

class GameObject;   

Furthermore, put guards in each header file.

抠脚大汉 2024-12-20 14:11:38

问题是您的 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" in Physics.h it gets included when GameObject.h includes Physics.h.

转角预定愛 2024-12-20 14:11:38

在所有 *.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 as g++ -Wall -H -c yourcode.cc

待天淡蓝洁白时 2024-12-20 14:11:38

首先你也需要在游戏对象上包含守卫,但这不是真正的问题

如果其他东西首先包含物理.h,物理.h包含游戏对象.h,你会得到这样的东西:

class GameObject {
...
};

#include physics.h

class Physics {
...
};

并且#include物理.h被丢弃,因为包含守卫,最终您会在声明物理之前声明游戏对象。

但如果您希望 GameObject 有一个指向物理的指针,那就是一个问题,因为对于 htat 物理必须首先声明。

要解决这个循环,您可以前向声明一个类,但前提是您仅在下面的声明中将其用作指针或引用,即:

#ifndef PHYSICS_H
#define PHYSICS_H

//  no need for this now #include "GameObject.h"

#include <list>

class GameObject;

class Physics
{
private:
    list<GameObject*> objects;
    list<GameObject*>::iterator i;
public:
    void ApplyPhysics(GameObject*);
    Vector2X CheckCollisions(Vector2X, GameObject*);
};

#endif // PHYSICS_H

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:

class GameObject {
...
};

#include physics.h

class Physics {
...
};

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.:

#ifndef PHYSICS_H
#define PHYSICS_H

//  no need for this now #include "GameObject.h"

#include <list>

class GameObject;

class Physics
{
private:
    list<GameObject*> objects;
    list<GameObject*>::iterator i;
public:
    void ApplyPhysics(GameObject*);
    Vector2X CheckCollisions(Vector2X, GameObject*);
};

#endif // PHYSICS_H
战皆罪 2024-12-20 14:11:38

所有头文件中使用包含防护。由于您使用的是 Visual Studio,因此您可以使用 #pragma Once 作为所有标头中的第一个预处理器定义。

不过,我建议使用经典方法:

#ifndef CLASS_NAME_H_
#define CLASS_NAME_H_

// Header code here

#endif //CLASS_NAME_H_

第二次阅读 转发声明并应用它。

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:

#ifndef CLASS_NAME_H_
#define CLASS_NAME_H_

// Header code here

#endif //CLASS_NAME_H_

Second read about forward declaration and apply it.

烟燃烟灭 2024-12-20 14:11:38

标头保护的目标是避免多次包含同一文件。
但目前C++中使用的头保护还可以改进。当前的守卫是:

#ifndef AAA_H
#define AAA_H

class AAA
{ /* ... */ };

#endif

我的新守卫建议是:

#ifndef AAA_H
#define AAA_H

class AAA
{ /* ... */ };

#else
class AAA;  // Forward declaration
#endif

这解决了当 AAA 类需要 BBB 类声明,而 BBB 类需要 AAA 类声明时出现的恼人问题,通常是因为从一个类到另一个类存在交叉指针:

// File AAA.h
#ifndef AAA_H
#define AAA_H

#include "BBB.h"

class AAA
{ 
  BBB *bbb;
/* ... */ 
};

#else
class AAA;  // Forward declaration
#endif

//+++++++++++++++++++++++++++++++++++++++

// File BBB.h
#ifndef BBB_H
#define BBB_H

#include "AAA.h"

class BBB
{ 
  AAA *aaa;
/* ... */ 
};

#else
class BBB;  // Forward declaration
#endif

我希望将其包含在自动从模板生成代码的 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:

#ifndef AAA_H
#define AAA_H

class AAA
{ /* ... */ };

#endif

My new guard proposal is:

#ifndef AAA_H
#define AAA_H

class AAA
{ /* ... */ };

#else
class AAA;  // Forward declaration
#endif

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:

// File AAA.h
#ifndef AAA_H
#define AAA_H

#include "BBB.h"

class AAA
{ 
  BBB *bbb;
/* ... */ 
};

#else
class AAA;  // Forward declaration
#endif

//+++++++++++++++++++++++++++++++++++++++

// File BBB.h
#ifndef BBB_H
#define BBB_H

#include "AAA.h"

class BBB
{ 
  AAA *aaa;
/* ... */ 
};

#else
class BBB;  // Forward declaration
#endif

I would love for this to be included in the IDEs that automatically generate code from templates.

话少情深 2024-12-20 14:11:38

" #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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文