清理旧版代码“标头意大利面条”

时间:2008-09-21 07:02:26

标签: c++ legacy-code

任何推荐的清理“标头意大利面条”的做法都是极其严重的 编译时间慢(Linux / Unix)?

GCC是否与“#pragma once”等同? (发现有关此事的相互矛盾的消息)

感谢。

11 个答案:

答案 0 :(得分:8)

假设您熟悉“包含警卫”(标题开头的#ifdef ..),另外一种加快构建时间的方法是使用外部包含警戒。 它在“Large Scale C++ Software Design”中讨论过。这个想法是经典包括守卫,不像#pragma一次,不要让你从第二次开始忽略头部所需的预处理器解析(即它仍然需要解析并寻找包含守卫的开始和结束。外部包括警卫你将#ifdef放在#include线本身周围。

所以它看起来像这样:

#ifndef MY_HEADER
#include "myheader.h"
#endif

当然在H档案中你有经典的包含守卫

#ifndef MY_HEADER
#define MY_HEADER

// content of header

#endif

这样,myheader.h文件甚至不被预处理器打开/解析,它可以在大型项目中节省大量时间,特别是当头文件位于共享远程位置时,就像它们有时一样。 / p>

再次,这一切都在那本书中。 HTH

答案 1 :(得分:6)

如果你想做一个完整的清理并有时间去做,那么最好的解决办法就是删除所有文件中的所有#includes(除了显而易见的文件,例如abc.cpp中的abc.h)然后编译该项目。添加必要的前向声明或标题以修复第一个错误,然后重复,直到你干净利落。

这不会解决可能导致包含问题的潜在问题,但它确实确保只包含必需的问题。

答案 2 :(得分:4)

我读过GCC认为#pragma once已被弃用,尽管即使#pragma once也只能做很多事情来加快速度。

要尝试解开#include意大利面条,您可以查看doxygen。它应该能够生成包含的标题的图形,这可以使您在简化事物方面具有优势。我无法回想起详细信息,但图表功能可能需要您安装GraphViz并告诉doxygen它可以找到GraphViz的dotty.exe的路径。

如果主要关注编译时间,您可能会考虑的另一种方法是设置Precompiled Headers

答案 3 :(得分:3)

理查德有点不对(为什么他的解决方案被记下来?)。

无论如何,所有C / C ++标题都应该使用内部包含保护。

这就是说:

1 - 您的遗留代码不再被维护了,您应该使用预编译的标头(这是一个黑客,但是嘿......您需要加快编译速度,而不是重构未维护的代码)

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(即使它可能稍慢一些:最终,前向声明无论如何都会包含干净无用的内容,所以你应该有一个更快的编译计时器。)

答案 4 :(得分:3)

我前几天读到了一个减少标头依赖性的巧妙技巧:编写一个

的脚本
  • 找到所有#include语句
  • 一次删除一个语句并重新编译
  • 如果编译失败,请在
  • 中添加include语句

最后,您希望最终得到代码中所需的最少内容。您可以编写一个类似的脚本,重新安排包含以确定它们是否自给自足,或者需要在它们之前包含其他标头(首先包括标头,查看编译是否失败,报告它)。这应该可以在某种程度上清理你的代码。

更多说明:

  • 现代编译器(其中包括gcc)识别标题保护,并以与pragma相同的方式进行优化,只打开文件一次。
  • pragma一旦在同一文件中的文件系统中有不同的名称(即使用软链接),就会出现问题

  • gcc支持#pragma一次,但称之为“过时”
  • 所有编译器都不支持
  • pragma,而不是C标准的一部分

  • 不仅编译器存在问题。像Incredibuild这样的工具也会出现#pragma once
  • 的问题

答案 5 :(得分:2)

使用其中一个或多个来加快构建时间

  1. 使用预编译标题
  2. 使用缓存机制(例如scons)
  3. 使用分布式构建系统(distcc,Incredibuild($))

答案 6 :(得分:0)

在标题中:仅当您不能使用前向声明时才包含标题,但始终#include您需要的任何文件(包括依赖项是邪恶的!)。

答案 7 :(得分:0)

正如在另一个答案中所提到的,你应该尽可能使用前向声明。据我所知,GCC没有任何等同于#pragma的东西,这就是为什么我坚持使用包含守卫的旧时尚风格。

答案 8 :(得分:0)

感谢您的回复,但问题是关于现有代码,其中包括严格的“包含订单”等。 问题是,是否有任何工具/脚本来澄清实际情况。

Header guard不是解决方案,因为它们不会阻止编译器一次又一次地读取整个文件......

答案 9 :(得分:0)

PC-Lint将大大有助于清理意大利面条标题。它也会为你解决其他问题,比如未初始化的变量,看不见等等。

答案 10 :(得分:0)

正如onebyone.livejournal.com在对您的问题的回复中评论时,一些编译器支持include guard optimization,我链接的页面定义如下:

  

包含保护优化是指编译器识别上述内部包含保护惯用语并采取措施避免多次打开文件。编译器可以查看包含文件,删除注释和空格,并确定整个文件是否在包含保护范围内。如果是,则存储文件名并在地图中包含保护条件。下次要求编译器包含该文件时,它可以检查包含保护条件并决定是否跳过该文件或#include它而不需要打开该文件。

然后,你已经回答说外部包含警卫不是你问题的答案。要解开必须包含在特定顺序中的头文件,我建议如下:

  • 每个.c.cpp文件应首先#include相应的.h文件,其余#include指令应按字母顺序排序。当这会破坏头文件之间未声明的依赖关系时,通常会出现构建错误。
  • 如果您有一个头文件,它定义了用于大多数代码的基本类型或全局#define指令的全局typedef,那么每个.h文件应首先#include该文件,其余的#include指令应按字母顺序排序。
  • 当这些更改导致编译错误时,您通常必须以#include的形式从一个头文件向另一个头文件添加显式依赖项。
  • 如果这些更改不会导致编译错误,则可能会导致行为更改。希望您有一些可用于验证应用程序功能的测试套件。

这听起来像问题的一部分可能是增量构建比它们应该慢得多。正如其他人所指出的那样,这种情况可以通过前向声明或分布式构建系统得到改善。