C++ #include guards

camsedfj  于 2023-05-02  发布在  其他
关注(0)|答案(8)|浏览(161)

我有三门课,GameEvents,Physics和GameObject。我有他们每个人的标题。GameEvents有一个Physics和一个GameObjects列表。物理学有一个游戏对象列表。
我试图实现的GameObject能够访问或拥有一个物理对象。
如果我在GameObject中简单地使用#include "Physics.h",我会得到我理解的error C2111: 'ClassXXX' : 'class' type redifinition。这就是我认为包含守卫会有帮助的地方,所以我在我的物理中添加了一个包含守卫。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

但如果我#包括“物理。h”在我的游戏对象中。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();
};

我收到了很多问题,不明白为什么他们会出现。如果我不把“物理”也包括进去。h”我的代码运行得很好。

gmxoilav

gmxoilav1#

预处理器是一个程序,它接受你的程序,做一些修改(例如包含文件(#include),宏扩展(#define),以及基本上以#开头的所有内容),并将“干净”的结果提供给编译器。
当预处理器看到#include时,它的工作原理是这样的:
当你写:

#include "some_file"

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

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

并且:

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

并且:

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是如何重新定义的。
当你编写guards时,它们会变成这样:

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 s是如何扩展的(这和前面的例子完全一样:复制粘贴)

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; };

因此,正如您所看到的,任何可以在同一个文件中获得#include d两次的东西,无论是直接还是间接,都需要加以保护。由于.h文件总是很有可能被包括两次,所以如果您保护所有的。h文件。
P.S.注意你也有圆形的#include s。想象一下,预处理器复制粘贴物理代码。h进入GameObject。h看到有一个#include "GameObject.h",这意味着将GameObject.h复制到它自己。当你复制时,你再次得到#include "Pysics.h",你永远陷入了一个循环。编译器可以防止这种情况,但这意味着#include已经完成了一半。
在说如何解决这个问题之前,你应该知道另一件事。
如果您有:

#include "b.h"

class A
{
    B b;
};

然后编译器需要知道关于b的一切,最重要的是,它有什么变量等,这样它就知道应该在A中放置多少字节来代替b
但是,如果您有:

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
}
kupeojn6

kupeojn62#

您在此处有循环引用:Physics.h包括GameObject.hGameObject.h包括Physics.h。您的类Physics使用GameObject*(指针)类型,因此您不需要在Physics.h中包含GameObject.h,而只需使用向前声明-而不是

#include "GameObject.h"

class GameObject;

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

rekjcdws

rekjcdws3#

问题是你的GameObject.h没有保护,所以当你在Physics.h中使用#include "GameObject.h"时,当GameObject.h包含Physics.h时,它会被包含。

5lhxktic

5lhxktic4#

在所有*.h*.hh头文件中添加include guards(除非您有特殊原因不这样做)。
要了解发生了什么,请尝试获取源代码的预处理形式。对于GCC,它类似于g++ -Wall -C -E yourcode.cc > yourcode.i(我不知道Microsoft编译器是如何做到这一点的)。您还可以询问包含哪些文件,GCC为g++ -Wall -H -c yourcode.cc

vmpqdwk3

vmpqdwk35#

首先,你需要在gameobject上包含守卫,但这不是真实的的问题
如果其他东西包括物理学。h首先,物理。h包括游戏对象。h,你会得到这样的结果:

class GameObject {
...
};

#include physics.h

class Physics {
...
};

包括物理学。h由于include guards而被丢弃,并且您最终在Physics声明之前声明了GameObject。

但是如果你想让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
56lgkhnf

56lgkhnf6#

在头文件中使用include guards。因为你使用的是Visual Studio,所以你可以使用#pragma once作为所有头文件中的第一个预处理器定义。
但是我建议使用经典的方法:

#ifndef CLASS_NAME_H_
#define CLASS_NAME_H_

// Header code here

#endif //CLASS_NAME_H_

第二次阅读向前声明并应用它。

c6ubokkw

c6ubokkw7#

头保护的目的是避免多次包含同一个文件。但是目前在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中。

knpiaxh1

knpiaxh18#

“#pragma once“::其作用与报头保护相同,并且具有更短和更不易出错的附加优点。
许多编译器使用#pragma指令支持更简单的头保护的替代形式:“#pragma once“//你的代码在这里
然而,#pragma once并不是 C++ 的正式组成部分,也不是所有的编译器都支持它(尽管大多数现代编译器都支持)。
出于兼容性的考虑,人们建议坚持使用传统的头罩。它们并不需要太多的工作,并且保证在所有兼容的编译器上都支持它们。

相关问题