在Visual Studio上"Redundant #include Guards"必要吗?

is "Redundant #include Guards" necessary on Visual Studio?

本文关键字:Guards Redundant Visual Studio #include      更新时间:2023-10-16

我过去常常使用下面的代码来确保include文件不会被加载多次。

#ifndef _STRING_
#include <string>
#endif
// use std::string here
std::string str;
...

这个技巧在《c++的API设计》一书中有说明。

现在我的同事告诉我在Visual Studio中没有必要这样做,因为如果string的实现头文件包含#pragma once,则不需要include保护来提高编译速度。

正确吗?

引自原著:

7.2.3 Redundant #include Guards
Another way to reduce the overhead of parsing too many include files is to add redundant preprocessor
guards at the point of inclusion. For example, if you have an include file, bigfile.h, that looks
like this
#ifndef BIGFILE_H
#define BIGFILE_H
// lots and lots of code
#endif
then you might include this file from another header by doing the following:
#ifndef BIGFILE_H
#include "bigfile.h"
#endif
This saves the cost of pointlessly opening and parsing the entire include file if you’ve already
included it.

通常术语'include guard'是指这个#ifdef, #define, #endif序列被放在这个文件中的特定头文件的内容周围。

许多c++编译器提供#pragma once语句,以保证在外部具有相同的行为。但是,为了便于C/c++代码的移植,我不赞成使用它。

UPDATE(根据OP的编辑)
此外,将#ifdef#endif放在另一个文件中的#include语句周围可能会阻止预处理器打开包含文件本身(从而略微减少编译时间和内存使用)。我希望#pragma once会自动做到这一点,但不能肯定(这可能是具体的实现)。

您不需要这样做,因为任何有能力的开发人员编写的头文件都会有自己的保护。你可以假设标准库头文件是由有能力的工程师编写的,如果你发现自己使用的是没有include保护的第三方头文件…好吧,这个第三方现在非常可疑……

至于写你自己的头,你可以使用标准的:

#ifndef MY_HEADER_H
#define MY_HEADER_H
// ...code
#endif

或者直接使用:

#pragma once

注意,这不是标准的C或c++,它是一个编译器扩展。它不会在每个编译器上工作,但使用它是你的决定,取决于你的预期用途。

根据定义,冗余包含保护是"冗余的"。它们不会影响通过编译创建的二进制文件。然而,它们确实有一个好处。冗余的包含保护可以减少编译时间。

谁在乎编译时间?我在乎。我只是一个开发人员,是一个由数百名开发人员组成的项目,在数千个源文件中包含数百万行源代码。一个完整的重建项目花了我45分钟。从修订控制拉出的增量构建花了我20多分钟。由于我的工作依赖于这个大项目,在等待这个长时间的构建时,我不能执行任何测试。如果将构建时间缩短到5分钟以内,我们公司将受益匪浅。假设构建时间节省为20分钟。1年* 100名开发者* 1次构建/天,* 1/3小时/次构建* 250天/年* 50美元/小时=每年节省416,667美元。应该有人关心这个。

对于Ed S,我已经使用冗余包含警卫10年了。偶尔您会发现有人使用这种技术,但大多数人都对它感到害羞,因为它可能会使代码看起来很丑。"#pragma once"看起来干净多了。从百分比来看,很少有开发人员会通过继续接受教育和学习技术来不断提高自己的才能。冗余的#include警卫技术有点晦涩,只有当有人费心对大型项目进行分析时,才会意识到它的好处。你知道有多少开发人员会不厌其烦地购买有关高级技术的c++书籍?

回到最初关于在Visual Studio中冗余Include守卫vs #pragma的问题…根据Wiki上的#pragma once,支持"#pragma once"的编译器可能比#include守卫更有效,因为它们可以分析文件名和路径,以防止加载已经加载的文件。有三个编译器的名字被提到具有这种优化。在这个列表中明显缺席的是Visual Studio。所以,我们仍然在想,在Visual Studio中,是否应该使用冗余的#include保护符,或者只使用一次#pragma。

对于中小型项目,#pragma一次当然很方便。对于大型项目,其中编译时间成为开发过程中的一个因素,冗余#include守卫使开发人员能够更好地控制编译过程。任何管理或架构大型项目的人都应该在他们的库中有大型c++设计——它讨论并推荐冗余的#include保护。

明智地使用#includes可能比冗余的include保护更有好处。随着c++模板和STL越来越流行,方法实现正在从。cpp文件迁移到。h文件。.cpp实现所具有的任何头文件依赖项现在都必须迁移到.h文件中。这增加了编译时间。我经常看到开发人员在他们的头文件中堆叠了许多不必要的#include,这样他们就不必费心识别他们真正需要的头文件。这也增加了编译时间。

#pragma once是一种更好的包含保护形式。如果您使用它,则不需要基于#define的include保护。

一般来说,这是一种更好的方法,因为它可以防止名称冲突破坏include保护。

也就是说,include保护应该在头文件中,而不是包装include。包装include应该是完全没有必要的(并且可能会让其他人感到困惑)。


编辑:

我认为我们在谈论两件不同的事情。我的问题是,当我们使用已经存在的头文件时,是否应该使用include guard,该头文件有#pragma一次或#ifndef xxx

在这种情况下,没有。如果头部有一个适当的保护,没有理由试图避免包括它。这只会增加混乱和复杂性。

包含保护符不是这样使用的。您不会将#include s包装在包含保护中。头文件应该将自己的内容包装在include保护中。当您编写的文件可能包含在其他文件中时,您应该这样做:

#ifndef _SOME_GUARD_
#define _SOME_GUARD_
// Content here
#endif

在Visual Studio的c++库实现中,可以通过string头包含#pragma once或检查#ifndef _STRING_来完成。