在 C/C++ 中多次包含头文件[重复]

Posted

技术标签:

【中文标题】在 C/C++ 中多次包含头文件[重复]【英文标题】:Including header files in C/C++ more than once [duplicate] 【发布时间】:2012-06-04 07:03:19 【问题描述】:

在 C 或 C++ 中多次包含头文件有用吗?

如果从不使用该机制,为什么编译器会担心包含两次文件?如果它真的没用,如果新的编译器确保每个头文件只包含一次,不是更方便吗?

编辑:

我知道有一些标准的方法来处理像include guards 和pragma once 这样的事情,但你为什么还要指定呢?只包含一次文件不应该是编译器的默认行为吗?

【问题讨论】:

是的,它应该是默认行为,因为这是常见的情况(他们可以定义一个新的预处理器指令#multipleincludes或其他东西,例如您想要多重包含的罕见情况)。但是 C 旨在使编译器易于从头开始编写,而不是易于编程。这在 1970 年代是有道理的,当时有许多通用架构,没有像 gcc 这样容易修改的开源编译器,而且程序很少超过 100k 行。这对 2012 年没有意义。始终将常见情况设为默认值! 我认为它不是重复的:它不质疑技术解决方案的目的,而是为了#include 的实际目的。 这个是我只想问的一个问题,而另一个不是。 我正在评估重新开放投票并决定保持关闭状态。即使这不是一个骗局,这个问题似乎本质上是推测性的,并且可能会吸引基于意见的答案——这也是本网站的题外话。 【参考方案1】:

是的,它在使用预处理器生成代码或执行 Boost.PP 之类的技巧时很有用。

有关示例,请参阅 X 宏。基本思想是该文件包含宏的主体,然后是 #define 参数,然后是 #include 它。这是一个人为的例子:

宏.xpp

std::cout << MESSAGE;
#undef MESSAGE

文件.cpp:

int main() 
# define MESSAGE "hello world"
# include "macro.xpp"

这还允许您在参数上使用#if 和朋友,这是普通宏无法做到的。

【讨论】:

【参考方案2】:

是的,包含多个标题可能很有用(尽管它相当不寻常)。典型的例子是&lt;assert.h&gt;,它定义asssert 的方式取决于NDEBUG 是否被定义。因此,包含它是有意义的,然后有一个(通常是有条件的)NDEBUG 定义,然后再次包含它,(至少可能)有不同的 assert 定义:

assert 宏每次都会根据NDEBUG 的当前状态重新定义 包含&lt;assert.h&gt;1

然而,大多数标头都在努力实现幂等性(即,无论包含多少次,都具有相同的效果)。


1C99, §7.2/1.

【讨论】:

【参考方案3】:

一个典型的例子(未经测试) - 要点是它考虑了一个枚举列表,因此它们在enum 和流代码中始终如一地出现:

// states.h
X(Uninitialised)
X(Initialised)
X(Running)
X(Complete)
X(Shutdown)

// app.c++
#if defined(X)
# error X is already defined
#endif

enum States 
    #define X(TAG) TAG,
    #include "states.h"
    #undef X
    Extra_So_Trailing_Comma_Ignored
;

std::ostream& operator<<(std::ostream& os, const States& state)

    #define X(TAG) if (state == TAG) return os << #TAG;
    #include "states.h"
    #undef X
    return os << "<Invalid-State>";

【讨论】:

在 C 中,枚举数列表中的尾随逗号是允许的(对于这种情况)。这在 C++ 中真的不同吗? @undur_gongor:挖了一点——似乎在 C++03 及更早版本中是不允许的,但在 C++11 中添加了容差。当然,即使在 C++03 下,特定的编译器也可能更加宽容。【参考方案4】:

是的,只包含一次会更方便,这就是您使用 #pragma once 的原因。在 C++ 中:)

编辑:

注意:#pragma once 是不可移植的。你可以使用

#ifndef FILENAME_H
#define FILENAME_H

如果您希望它是可移植的,则放在头文件的顶部。

【讨论】:

/我一直不明白为什么人们会选择非便携式解决方案,而某些 ifdef 并不难。 C++ 中使用标头保护。在不可移植的 C++ 中,您使用 #pragma 一次 /me 也从来不理解在包含守卫中强制使用起始下划线;) 保留下划线大写。只需从您的项目名称开始。 @Als:我知道较新的 MSVC、GCC 和 clang 编译器都支持它,但它仍然不是标准 C++ 并且包含防护也不是很难。至于#pragma once 更有效,我很想看到一些比较,因为至少 GCC 和 clang 识别包含守卫并将它们视为与#pragma 非常相似。【参考方案5】:

当您需要一些不想手动维护的“无聊”代码生成时,可以使用多次包含。

经典示例是 C/C++ 枚举和相应的字符串,它们或多或少看起来像这样:

// MYENUM_VALUES.H
MYENUMVALUE(a)
MYENUMVALUE(b)
MYENUMVALUE(c)
MYENUMVALUE(d)

// MYENUM.H
#define MYENUMVALUE(x) x,
enum MyEnum

#include "MYENUM_VALUES.H"

#undef MYENUMVALUE

// MYENUMTOSTRING.C
#define MYENUMVALUE(x) case x : return #x;

const char * MyEnumToString(MyEnum val)

    switch (val)
    
    #include "MYENUM_VALUES.H"
    default return "unknown";
    
 
#undef MYENUMVALUE

【讨论】:

【参考方案6】:
#ifndef _INC_HEADER_
#define _INC_HEADER_

//header code

#endif

HEADER 是标头的名称

例如。 main_win.h 将是 _INC_MAIN_WIN_

【讨论】:

下划线大写是保留的,不应使用。 不要被你的说法弄糊涂了:Where HEADER is the header's name你可以给任何名字,不是强制性的,它必须是头文件名。

以上是关于在 C/C++ 中多次包含头文件[重复]的主要内容,如果未能解决你的问题,请参考以下文章

使用预编译头提高编译速度

C/C++ 头文件中各包含哪些函数

(40)头文件

C:防止头文件重复包含

#ifndef#define#endif防止头文件重复包含

C++中防止多次包含头文件:#pragma once和 #indef的区别