确保标头明确包含在CPP文件中


9

我认为,#include对于CPP文件中使用的任何类型的标头,通常都是一种好习惯,无论该HPP文件中已经包含了什么。因此#include <string>,例如,即使在CPP中跳过它,我仍然可以编译,但我可能同时在HPP和CPP中使用。这样,我不必担心我的HPP是否使用前向声明。

是否有任何工具可以强制执行此#include编码样式?我应该执行这种编码样式吗?

由于预处理器/编译器不在乎#include是来自HPP还是CPP,因此如果我忘记遵循这种样式,则不会得到任何反馈。

Answers:


7

这是那些“应该”而不是“应该”的编码标准之一。原因是您几乎必须编写C ++解析器才能执行它。

头文件的一个非常普遍的规则是它们必须独立存在。头文件在包含相关头文件之前,不得要求#include其他一些头文件。这是一个可测试的要求。给定一些随机标头foo.hh,以下代码应编译并运行:

#include "foo.hh"
int main () {
   return 0;
}

对于在某些标头中使用其他类,此规则具有影响。有时可以通过预先声明其他类来避免这些后果。对于许多标准库类而言,这是不可能的。无法转发声明诸如std::string或的模板实例std::vector<SomeType>#include即使类型的唯一用途是作为函数的参数,也必须在标题中包含那些STL标题。

另一个问题是您偶然拖入的东西。例如:考虑以下问题:

文件foo.cc:

#include "foo.hh"
#include "bar.hh"

void Foo::Foo () : bar() { /* body elided */ }

void Foo::do_something (int item) {
   ...
   bar.add_item (item);
   ...
}

barFoo类型为的类数据成员Bar。您在这里做了正确的事情,并且包含了#include bar.hh,即使那必须已经包含在定义class的标头中Foo。但是,你有没有包括所使用的材料Bar::Bar()Bar::add_item(int)。在许多情况下,这些调用可能会导致其他外部引用。

如果您foo.o使用诸如之类的工具进行分析nm,则似乎其中的函数foo.cc正在调用您尚未完成的所有事情#include。那么,您应该#include为那些偶然的外部引用添加指令foo.cc吗?答案绝对不是。问题在于,很难将偶然调用的功能与直接调用的功能区分开。


2

我应该执行这种编码样式吗?

可能不是。我的规则是:头文件包含不能依赖于顺序。

您可以使用以下简单规则轻松地验证这一点:xc包含的第一个文件是xh


1
您能详细说明一下吗?我看不到如何真正验证顺序独立性。
2013年

1
它并不能真正保证订单的独立性-它只是确保订单的有效性#include "x.h"而无需事先要求#include。如果您不虐待,那就足够了#define
凯文·克莱恩

1
哦,我明白了。那还是个好主意。
2013年

2

如果您需要执行特定头文件必须独立的规则,则可以使用现有工具。创建一个基本的makefile,该文件单独编译每个头文件,但不生成目标文件。您将能够指定以哪种模式(C或C ++模式)编译头文件,并验证它可以独立存在。您可以做出合理的假设,即输出不包含任何误报,声明了所有必需的依赖项并且输出是准确的。

如果使用的是IDE,则可能无需makefile即可完成此操作(取决于您的IDE)。只需创建一个其他项目,添加要验证的头文件,然后更改设置以将其编译为C或C ++文件即可。例如,在MSVC中,您可以在“配置属性->常规”中更改“项目类型”设置。


1

我认为不存在这样的工具,但是如果有其他答案反驳我,我会很高兴。

编写此类工具的问题在于,它很容易报告错误的结果,因此,我估计此类工具的净收益接近于零。

这种工具唯一可行的方法是,它可以将其符号表仅重置为它处理的头文件的内容,但是随后遇到的问题是,构成库的外部API的头将实际的声明委托给了它。内部标题。
例如,<string>在GCC中,libc ++实现不声明任何内容,而仅包含一堆包含实际声明的内部标头。如果该工具将其符号表重置为仅由<string>自身声明的符号表,那将什么都没有。
您可以让该工具区分#include ""和和#include <>,但是如果外部库用来#include ""在API中包含其内部标头,那将无济于事。


1

没有#Pragma once做到这一点?您可以根据需要直接或通过链接的include包含很多次,并且只要#Pragma once它们旁边都有一个,标题仅包含一次。

至于执行它,也许您可​​以创建一个构建系统,该构建系统仅包括每个标头本身以及一些伪主要功能,以确保其可编译。#ifdef该测试方法包括最佳结果链。


1

始终在CPP文件中包含头文件。如果您决定使用预编译的头文件,这不仅显着缩短了编译时间,而且还为您节省了很多麻烦。以我的经验,即使遇到前向声明的麻烦也是值得实践的。仅在必要时才打破规则。


您能解释一下这将如何“大大缩短编译时间”吗?
Mawg说恢复Monica

1
这样可以确保每个编译单元(CPP文件)在编译时仅提取最少的包含文件。否则,如果将包含文件放入H文件中,则会迅速形成错误的依赖关系链,最终导致每次编译都包含所有包含文件。
Gvozden '16

使用include防护,我可能会“显着地”询问,但是我认为磁盘访问(为了发现那些序贯的标准)是“缓慢的”,因此,这一点将成为现实。感谢澄清
Mawg说起用莫妮卡

0

我要说的是,这项公约既有优点也有缺点。一方面,很高兴确切地知道您的.cpp文件包括什么。另一方面,包含的列表很容易增长到可笑的大小。

鼓励使用此约定的一种方法是,不要在自己的标头中包含任何内容,而应仅在.cpp文件中包含任何内容。这样,除非您显式包括它依赖的所有其他头,否则使用您的头的任何.cpp文件都不会编译。

在这里可能会做出一些合理的妥协。例如,您可能决定可以在自己的标头中包含标准库标头,但不能再包含其他标头。


3
如果您将include保留在标题之外,那么包含顺序就变得很重要了……我绝对想避免这种情况。
M. Dudley

1
-1:创建依赖项噩梦。对xh的增强可能需要更改包括xh在内的每个.cpp文件
kevin cline

1
@ M.Dudley,就像我说的那样,有优点也有缺点。
迪马
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.