清理旧版代码"标头意大利面"
任何建议的清理"标头意大利面"的做法都会引起极大的后果
编译时间慢(Linux / Unix)?
GCC是否有等同于" #pragma一次"的功能?
(发现与此相关的冲突消息)
谢谢。
解决方案
在标头中:仅当我们不能使用前向声明时才包含标头,但始终#include我们需要的任何文件(include依赖关系是邪恶的!)。
如另一个答案中所述,我们绝对应尽可能使用前向声明。据我所知,GCC一次都没有等同于#pragma的东西,这就是为什么我坚持包含警卫的旧时尚风格的原因。
感谢答复,但问题是关于包含严格的"包含顺序"等的现有代码。
问题是是否有任何工具/脚本来阐明实际情况。
标头保护解决方案,因为它们不会阻止编译器一次又一次地读取整个文件并...
我读过GCC认为" #pragma一旦"已被弃用,尽管即使" #pragma一次"也只能做很多事情来加快处理速度。
要尝试解开" #include"意大利面,可以查看Doxygen。它应该能够生成包含的标头的图形,这可能使我们在简化事情上有优势。我不记得这些详细信息,但是图形功能可能需要我们安装GraphViz,并告诉Doxygen可以在其中找到GraphViz的dotty.exe的路径。
如果我们最关心的是编译时间,则可能要考虑的另一种方法是设置预编译头。
使用其中一个或者多个来加快构建时间
- 使用预编译头
- 使用缓存机制(例如,conson)
- 使用分布式构建系统(distcc,Incredibuild($))
假设我们熟悉" includeguards"(在标题开头是#ifdef。),则可以通过使用外部includeguards来加快构建时间。
在"大型C ++软件设计"中进行了讨论。这个想法是,经典的include防护与#pragma一次不同,它不会为我们节省从第二次开始就忽略标头所需的预处理程序解析(即,它仍然必须解析并寻找include防护的开始和结束。外部包含卫兵,我们可以将#ifdef放置在#include线本身的周围。
所以看起来像这样:
#ifndef MY_HEADER #include "myheader.h" #endif
当然,在H文件中,我们还可以使用经典的包含保护功能
#ifndef MY_HEADER #define MY_HEADER // content of header #endif
通过这种方式,myheader.h文件甚至不会被预处理器打开/解析,并且可以在大型项目中为我们节省很多时间,尤其是在头文件有时位于共享远程位置的情况下。
再次,全部都在那本书中。 hth
如果我们想进行一次完整的清理并有时间去做,那么最好的解决方案是删除所有文件中的所有#includes(明显的文件除外,例如abc.cpp中的abc.h),然后编译项目。添加必要的前向声明或者标头以解决第一个错误,然后重复进行直到干净地完成为止。
这不能解决可能导致包含问题的潜在问题,但是可以确保仅包含必需的内容。
PC-Lint在清理意大利面条头上将有很长的路要走。它还会为我们解决其他问题,例如未初始化的变量不可见等。
理查德是对的(为什么他的解决方案被记录下来了?)。
无论如何,所有C / C ++标头都应使用内部包含保护。
这样说:
1旧代码不再真正维护,我们应该使用预编译的标头(这是hack,但是...我们需要的是加快编译速度,而不是重构未维护的代码)
2旧代码仍然有效。然后,我们可以使用预编译的标头和/或者防护/外部防护作为临时解决方案,但是最后,我们需要一次删除所有包含,一个.C或者.CPP,然后分别进行编译。一次C或者.CPP文件,使用前向声明或者在需要时更正它们的包含(甚至将较大的包含分解为较小的包含,以确保每个.C或者.CPP文件仅获得所需的标头)。无论如何,测试和删除过时的包含项是项目维护的一部分,因此...
我自己对预编译头文件的体验并不十分出色,因为一半的时间,编译器找不到我定义的符号,因此我尝试使用完整的"清理/重建",以确保它不是预编译头文件那已经过时了。因此,我的猜测是将其用于我们甚至不会碰到的外部库(例如STL,C API头文件,Boost等)。不过,我自己的经验是使用Visual C ++ 6,所以我想(希望吗?)他们现在做对了。
现在,最后一件事:标头应始终自给自足。这意味着,如果标题的包含取决于包含的顺序,那么我们将遇到问题。例如,如果我们可以编写:
#include "AAA.hpp" #include "BBB.hpp"
但不是:
#include "BBB.hpp" #include "AAA.hpp"
因为BBB依赖于AAA,所以我们所拥有的只是我们从未在代码中确认的依赖项。不使用定义来确认它只会使编译成为噩梦。 BBB也应该包含AAA(即使它可能会更慢:最终,前向声明也将清除无用的包含,因此我们应该有一个更快的编译计时器)。
正如onebyone.livejournal.com在回答问题时所说,某些编译器支持包括防护优化,我链接的页面定义如下:
The include guard optimisation is when a compiler recognises the internal include guard idiom described above and takes steps to avoid opening the file multiple times. The compiler can look at an include file, strip out comments and white space and work out if the whole of the file is within the include guards. If it is, it stores the filename and include guard condition in a map. The next time the compiler is asked to include the file, it can check the include guard condition and make the decision whether to skip the file or #include it without needing to open the file.
再一次,我们已经回答了外部包含防护不是我们问题的答案。为了解开必须按特定顺序包含的头文件,我建议以下内容:
- 每个
.c
或者.cpp
文件都应首先#include
相应的.h
文件,其余的#include指令应按字母顺序排序。当这破坏了头文件之间未声明的依赖性时,通常会出现构建错误。 - 如果我们有一个头文件定义了用于大多数代码的基本类型的全局typedef或者全局的#define指令,则每个.h文件应首先包含该文件,其余的应包含在该文件中。 #include`指令应按字母顺序排序。
- 当这些更改导致编译错误时,通常必须以#include的形式从一个头文件向另一个头文件添加显式依赖项。
- 当这些更改没有引起编译错误时,它们可能会导致行为更改。希望我们可以使用某种测试套件来验证应用程序的功能。
听起来这似乎也是问题的一部分,因为增量构建的速度比预期的要慢得多。正如其他人指出的那样,可以通过使用前向声明或者分布式构建系统来改善这种情况。
前几天,我读到了一个减少标题依赖的巧妙技巧:编写一个脚本,该脚本将
- 找到所有的#include语句
- 一次删除一条语句并重新编译
- 如果编译失败,请在后面添加include语句
最后,希望我们最终得到代码中所需的最少包含量。我们可以编写类似的脚本来重新排列包括的内容,以查明它们是否是自给自足的,或者要求在它们之前包含其他标头(首先包含标头,请参见编译是否失败,然后进行报告)。这应该可以清除代码。
更多注意事项:
- 现代编译器(其中包括gcc)可以识别标头保护符,并且可以像一次编译指示一样进行优化,只打开一次文件。
- 如果同一个文件在文件系统中具有不同的名称(例如,具有软链接),则一次编译可能会出现问题
- gcc一次支持#pragma,但称其为"过时"
- 编译器曾经不被所有编译器支持,也不属于C标准
- 不仅编译器可能会出现问题。像Incredibuild之类的工具也曾经遇到过#pragma问题