Answers:
这是那些“应该”而不是“应该”的编码标准之一。原因是您几乎必须编写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);
...
}
这bar
是Foo
类型为的类数据成员Bar
。您在这里做了正确的事情,并且包含了#include bar.hh,即使那必须已经包含在定义class的标头中Foo
。但是,你有没有包括所使用的材料Bar::Bar()
和Bar::add_item(int)
。在许多情况下,这些调用可能会导致其他外部引用。
如果您foo.o
使用诸如之类的工具进行分析nm
,则似乎其中的函数foo.cc
正在调用您尚未完成的所有事情#include
。那么,您应该#include
为那些偶然的外部引用添加指令foo.cc
吗?答案绝对不是。问题在于,很难将偶然调用的功能与直接调用的功能区分开。
我认为不存在这样的工具,但是如果有其他答案反驳我,我会很高兴。
编写此类工具的问题在于,它很容易报告错误的结果,因此,我估计此类工具的净收益接近于零。
这种工具唯一可行的方法是,它可以将其符号表仅重置为它处理的头文件的内容,但是随后遇到的问题是,构成库的外部API的头将实际的声明委托给了它。内部标题。
例如,<string>
在GCC中,libc ++实现不声明任何内容,而仅包含一堆包含实际声明的内部标头。如果该工具将其符号表重置为仅由<string>
自身声明的符号表,那将什么都没有。
您可以让该工具区分#include ""
和和#include <>
,但是如果外部库用来#include ""
在API中包含其内部标头,那将无济于事。
没有#Pragma once
做到这一点?您可以根据需要直接或通过链接的include包含很多次,并且只要#Pragma once
它们旁边都有一个,标题仅包含一次。
至于执行它,也许您可以创建一个构建系统,该构建系统仅包括每个标头本身以及一些伪主要功能,以确保其可编译。#ifdef
该测试方法包括最佳结果链。
始终在CPP文件中包含头文件。如果您决定使用预编译的头文件,这不仅显着缩短了编译时间,而且还为您节省了很多麻烦。以我的经验,即使遇到前向声明的麻烦也是值得实践的。仅在必要时才打破规则。
我要说的是,这项公约既有优点也有缺点。一方面,很高兴确切地知道您的.cpp文件包括什么。另一方面,包含的列表很容易增长到可笑的大小。
鼓励使用此约定的一种方法是,不要在自己的标头中包含任何内容,而应仅在.cpp文件中包含任何内容。这样,除非您显式包括它依赖的所有其他头,否则使用您的头的任何.cpp文件都不会编译。
在这里可能会做出一些合理的妥协。例如,您可能决定可以在自己的标头中包含标准库标头,但不能再包含其他标头。