C++ #include 守卫
Posted
技术标签:
【中文标题】C++ #include 守卫【英文标题】:C++ #include guards 【发布时间】:2011-12-22 15:16:24 【问题描述】:已解决
真正帮助我的是我可以#include .cpp 文件中的标头而不会导致重新定义的错误。
我是 C++ 新手,但我有一些 C# 和 Java 编程经验,所以我可能会遗漏一些 C++ 独有的基本知识。
问题是我真的不知道哪里出了问题,我会贴一些代码来尝试解释问题。
我有三个类,GameEvents、Physics 和 GameObject。我有他们每个人的标题。 GameEvents 有一个 Physics 和一个 GameObjects 列表。 Physics 有一个 GameObjects 列表。
我想要实现的是我希望 GameObject 能够访问或拥有一个物理对象。
如果我只是在 GameObject 中 #include "Physics.h" 我会得到 “错误C2111:'ClassXXX':'class'类型重新定义”我理解。 这就是我认为#include-guards 会有所帮助的地方,所以我在我的 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" 我的代码运行得很好。
我非常感谢任何帮助。
【问题讨论】:
***.com/questions/8010601/… 和许多其他人的副本。 【参考方案1】:预处理器是一个程序,它接受您的程序,进行一些更改(例如包含文件 (#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
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 文件。
附:请注意,您还有通知 #include
s。想象一下预处理器将 Physics.h 的代码复制粘贴到 GameObject.h 中,看到有一个 #include "GameObject.h"
,这意味着将 GameObject.h
复制到自身中。当你复制时,你又会得到#include "Pysics.h"
,你会永远陷入循环中。编译器会阻止这种情况,但这意味着您的 #include
s 已经完成了一半。
在说如何解决这个问题之前,你应该知道另一件事。
如果你有:
#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
【讨论】:
谢谢,这对我帮助很大,我对正在发生的事情有了更好的了解。我已在我的 GameObject.h 中成功转发声明了 Physics *physics。但如果我理解正确,我无法在我的 GameObject 中创建物理,我必须将现有指针传递给我的 GameObject.cpp? 我似乎无法理解如何访问我的 GameObject.cpp 中的物理指针如果我尝试物理->CheckCollisions() 我得到“不允许指向不完整类类型的指针”并且没有成员找到了。 @Orujimaru 我在GameObject
类中没有看到Physics
对象或指针,但我在Physics
类中看到了很多GameObject *
s。这建议在Physics.h
中向前声明GameObject
,而不是相反。无论哪种方式,当您想使用该类(即在.cpp
文件中)时,您需要包含每个标头本身。由于您不包含 .cpp
文件,因此不存在循环包含的风险!
@hans,不不,通过#define
定义的符号包含在同一行中。例如#define SOME_SYMBOL (some_expression)
意味着预处理器将用(some_expression)
替换它从该点看到的SOME_SYMBOL
。如果你有#define SOME_SYMBOL
,前面什么都没有,它会在它看到它的时候用任何东西替换它。所以#define A_H
的意思是“定义一个名为A_H
的符号,它什么都不替换”。对我们来说重要的部分是“定义一个名为 A_H
的符号”,因为稍后我们想要一个关于是否定义符号的 if (#ifndef
)。
所以,class A int a
与 #define A_H
无关,它确实是 #ifndef...#endif' right? Also
#define A_H` 的一部分,A_H
没有被分配,只是为了正式定义 A_H
对吧?【参考方案2】:
您在此处有循环引用:Physics.h
包括 GameObject.h
,其中包括 Physics.h
。您的课程Physics
使用GameObject*
(指针)类型,因此您不需要在Physics.h
中包含GameObject.h
,而只需使用前向声明 - 而不是
#include "GameObject.h"
放
class GameObject;
此外,在每个头文件中放置守卫。
【讨论】:
【参考方案3】:问题是你的GameObject.h
没有警卫,所以当你在Physics.h
中的#include "GameObject.h"
时,当GameObject.h
包含Physics.h
时,它会被包含在内。
【讨论】:
【参考方案4】:在您的所有 *.h
或 *.hh
头文件中添加包含保护(除非您有特定的理由不这样做)。
要了解发生了什么,请尝试获取源代码的预处理形式。对于 GCC,它类似于g++ -Wall -C -E yourcode.cc > yourcode.i
(我不知道 Microsoft 编译器是如何做到这一点的)。也可以询问包含哪些文件,GCC为g++ -Wall -H -c yourcode.cc
【讨论】:
【参考方案5】:首先你也需要在游戏对象上包含守卫,但这不是这里真正的问题
如果其他的东西首先包括physics.h,physics.h 包括gameobject.h,你会得到这样的结果:
class GameObject
...
;
#include physics.h
class Physics
...
;
并且由于包含保护,#include Physics.h 被丢弃,并且您最终会在声明 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
【讨论】:
我已经为所有标题添加了保护,但是当我用 Class GameObject 替换 #include "GameObject.h" 时; Visual Studio 为 Physics.cpp 和 Physics.h 中的每一行代码返回错误。如果我尝试在任何标头中转发声明任何其他类,也会发生同样的情况。【参考方案6】:在 ALL 头文件中使用包含保护。由于您使用的是 Visual Studio,因此您可以使用 #pragma once
作为所有标头中的第一个预处理器定义。
但我建议使用经典方法:
#ifndef CLASS_NAME_H_
#define CLASS_NAME_H_
// Header code here
#endif //CLASS_NAME_H_
第二次阅读forward declaration 并应用它。
【讨论】:
【参考方案7】:标头保护的目标是避免多次包含同一个文件。 但是目前在 C++ 中使用的 header 保护可以改进。目前的守卫是:
#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 中。
【讨论】:
【参考方案8】:" #pragma once " ::: 与标头保护具有相同的目的,并且具有更短且不易出错的额外好处。
许多编译器使用#pragma 指令支持更简单的替代形式的头文件保护: “#pragma once” // 你的代码在这里
但是,#pragma once 不是 C++ 语言的官方部分,并非所有编译器都支持它(尽管大多数现代编译器都支持)。
出于兼容性目的,人们建议坚持使用传统的头部保护。它们的工作量并不多,而且保证所有兼容的编译器都支持它们。
【讨论】:
以上是关于C++ #include 守卫的主要内容,如果未能解决你的问题,请参考以下文章