C ++ #include guards

时间:2011-11-05 12:16:28

标签: c++ header include include-guards

解决

真正帮助我的是,我可以在.cpp文件中#include标头而不会导致重新定义的错误。


我是C ++的新手,但我在C#和Java方面有一些编程经验,所以我可能会遗漏一些C ++独有的基础知识。

问题是我真的不知道什么是错的,我会粘贴一些代码来试图解释这个问题。

我有三个类,GameEvents,Physics和GameObject。我有每个标题。 GameEvents有一个Physics和一个GameObjects列表。 Physics有一个GameObjects列表。

我想要实现的是我希望GameObject能够访问或拥有一个Physics对象。

如果我只是在GameObject中#include“Physics.h”我得到了 “错误C2111:'ClassXXX':'类'类型redtifinition”我明白了。 这就是我认为#include-guard有帮助所以我在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”我的代码运行得很好。

我非常感谢任何帮助。

8 个答案:

答案 0 :(得分:114)

预处理器是一个程序,它接受你的程序,进行一些更改(例如包括文件(#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是如何重新定义的。

当你写警卫时,他们会变成这样:

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

正如您所看到的,任何可以在同一文件中两次获得#include d的内容,无论是直接还是间接都需要加以保护。由于.h文件总是很可能被包含两次,所以如果你保护所有你的.h文件就好了。

P.S。请注意,您还有循环#include s。想象一下预处理器将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
}

答案 1 :(得分:5)

您在此处有循环引用:Physics.h包括GameObject.h,其中包含Physics.h。您的班级Physics使用GameObject*(指针)类型,因此您无需在GameObject.h中加入Physics.h,只需使用转发声明 - 而不是

#include "GameObject.h" 

class GameObject;   

此外,在每个头文件中放置警卫。

答案 2 :(得分:4)

问题在于,您的GameObject.h没有警卫,所以#include "GameObject.h" Physics.h GameObject.h Physics.h包括{{1}}时,{{1}}。{ p>

答案 3 :(得分:4)

在所有*.h*.hh标头文件中添加包含警戒(除非您有特殊原因不这样做)。

要了解正在发生的事情,请尝试获取源代码的预处理形式。对于GCC,它类似于g++ -Wall -C -E yourcode.cc > yourcode.i(我不知道微软编译器如何做到这一点)。您还可以询问包含哪些文件,GCC为g++ -Wall -H -c yourcode.cc

答案 4 :(得分:4)

首先你需要在游戏对象上加入警卫,但这不是真正的问题

如果还有其他内容包括physics.h,那么physics.h包含gameobject.h,你可以得到类似的内容:

class GameObject {
...
};

#include physics.h

class Physics {
...
};

并且#include physics.h由于包含警卫而被丢弃,并且在物理声明之前你最终会得到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

答案 5 :(得分:3)

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

但我建议使用经典方法:

#ifndef CLASS_NAME_H_
#define CLASS_NAME_H_

// Header code here

#endif //CLASS_NAME_H_

关于forward declaration的第二次阅读并应用它。

答案 6 :(得分:0)

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

var firms = _context.Firms.AsQueryable();

firms = firms
                .SelectMany(x => x.Categories)
                .Where(x => x.CategoryId == param.CategoryId)
                .Select(x => x.Firm)
                .Include(x => x.Categories).ThenInclude(x => x.Category)
                .Include(x => x.Services).ThenInclude(x => x.Service);
                ;

我的新警卫建议是:

#ifndef AAA_H
#define AAA_H

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

#endif

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

#ifndef AAA_H
#define AAA_H

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

#else
class AAA;  // Forward declaration
#endif

我希望将其包含在从模板自动生成代码的IDE中。

答案 7 :(得分:0)

“ #pragma一旦” :::具有与标头防护相同的目的,并具有更短,更不易出错的优点。

许多编译器使用#pragma指令支持更简单的备用表头防护形式: “ #pragma一次” //您的代码在这里

但是,#pragma曾经不是C ++语言的正式组成部分,并且并非所有编译器都支持它(尽管大多数现代编译器都支持)。

出于兼容性目的,人们建议坚持使用传统的标题保护器。它们工作量不大,而且可以保证所有兼容的编译器都支持它们。