如何在一个大型C++项目中检测不必要的#include文件?

102

我正在使用Visual Studio 2008开发一个庞大的C++项目,其中有许多不必要的#include指令。有时候这些#include只是遗留问题,如果将它们移除,所有文件都能够成功编译;在其他情况下,类可以被前向声明,而#include指令可以移到对应的.cpp文件中。是否有一些好用的工具可以检测到这两种情况?

20个回答

50

虽然它不会显示不需要的包含文件,但Visual Studio有一个设置/showIncludes(右键单击.cpp文件,选择属性>C/C++>高级),可以在编译时输出所有已包含文件的树形结构。 这可以帮助识别不应该被包含的文件。

您还可以查看pimpl习语,让您少依赖头文件,以便更容易地看到您可以删除的无用信息。


1
/showincludes 真是太好了。如果没有它,手动完成这项工作将会很困难。 - shambolic
1
这个输出在哪里?为了让未来的读者更快地阅读,您需要在解决方案资源管理器中右键单击文件。 - Czarking

30

PC Lint非常适合这项工作,并且它还可以为您找到各种其他的问题。 它具有命令行选项,可用于在Visual Studio中创建外部工具,但我发现Visual Lint插件更易于使用。 即使是Visual Lint的免费版本也很有帮助。 但请尝试一下PC-Lint。 调整其配置以避免给出太多警告需要一些时间,但是您会惊讶于它所发现的问题。


3
有关如何使用pc-lint的一些说明可以在http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318找到。 - David Sykes

29

26

!!免责声明!! 我在一款商业静态分析工具(非PC Lint)上工作。!!免责声明!!

简单的非语法解析方法存在以下几个问题:

1) 函数重载集合:

可能会出现重载函数具有来自不同文件的声明。删除其中一个头文件可能导致选择不同的重载函数而不是编译错误!结果是语义上的默默改变,这可能非常难以追踪。

2) 模板特化:

与重载示例类似,如果模板有部分或完全特化,当使用模板时需要将它们全部可见。主模板的特化可能位于不同的头文件中。移除具有特化的头文件不会导致编译错误,但如果该特化被选择,则可能导致未定义的行为。(参考: C++函数模板特化的可见性)

正如“msalters”所指出的那样,对代码进行全面分析还允许分析类的使用情况。通过检查类在文件路径的特定路径上的使用方式,可以完全删除类的定义(因此也删除了所有依赖项),或者至少将其移动到更接近包含树的主源的级别。


13
很难回答这个问题而不使它变成销售推广 - 提前道歉。在我看来,您需要考虑的是,在任何相当大的项目中,一个优秀的工程师要花费多长时间才能找到类似这样的问题(以及许多其他语言/控制流错误)。随着软件的更改,同样的任务需要一遍又一遍地重复。因此,当您计算节省的时间量时,这种工具的成本可能并不重要。 - Richard Corden

11

我不知道有任何这样的工具,过去我曾考虑编写一个,但问题在于这是一个难以解决的问题。

假设你的源文件包含a.h和b.h; a.h 包含 #define USE_FEATURE_X,而 b.h 使用了 #ifdef USE_FEATURE_X。如果注释掉 #include "a.h",你的文件可能仍然可以编译,但结果可能与你预期的不同。通过程序检测出这个问题是不容易的。

任何能够实现这一功能的工具都需要了解你的构建环境。例如,如果a.h看起来像这样:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

如果定义了WINNT,则USE_FEATURE_X才被定义,因此工具需要知道编译器本身生成哪些指令,以及哪些指令是在编译命令中而不是在头文件中指定的。


9

和Timmermans一样,我不熟悉任何此类工具。但我认识的程序员会编写Perl(或Python)脚本,尝试逐个注释每个include行,然后编译每个文件。


看起来现在Eric Raymond 有一个这样的工具

Google的cpplint.py有一个“使用什么就包含什么”的规则(还有许多其他规则),但据我所知,没有“包含你使用的内容”。即便如此,它仍然很有用。


当我读到这篇文章时,我不禁笑了起来。我的老板在我们的一个项目中就做了这件事情,而且是在上个月。他将头文件包含量减少了几个因素。 - Don Wakefield
2
Codewarrior在Mac上曾经内置了一个脚本来实现这个功能,即注释掉、编译、出错时取消注释,并继续到#include的结尾。它只适用于文件顶部的#include,但通常它们就在那里。虽然不完美,但它确实可以保持事情相对清晰。 - slycrel
deheader不幸地建议程序员不要在foo.cpp中包含foo.hpp。 - mcandre

4

4

试试Include Manager,它可以轻松集成到Visual Studio中,并可视化您的包含路径,帮助您找到不必要的内容。内部使用Graphviz,但还有许多更酷的功能。虽然它是商业产品,但价格非常低廉。


4

如果您对这个主题有兴趣,您可能想查看Lakos的大规模C++软件设计。它有点过时,但涵盖了许多“物理设计”问题,如找到需要包含的头文件的绝对最小值。我真的没有在其他任何地方看到过这种讨论。


3
如果您的头文件通常以以下方式开始
#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#endif

(与使用 #pragma once 相反)您可以将其更改为:
#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#else 
#pragma message("Someheader.h superfluously included")
#endif

由于编译器输出正在编译的cpp文件的名称,这将至少让您知道是哪个cpp文件导致了多次引入头文件。


12
我认为多次包含头文件是可以的。将使用的内容包含进去是好的,不应该依赖于包含文件。我认为原帖作者想要找到实际未使用的 #include。 - Ryan Ginstrom
12
IMO这样做是错误的。当头文件之间存在依赖关系时,应该包含其他头文件。如果你需要包含A.hB.h这两个文件,而它们都依赖于C.h,那么你会包含两次C.h,但这没问题,因为编译器会在第二次跳过它。如果不这样做,你就必须记住总是先包含C.h再包含A.hB.h,这样会导致更多无用的包含。 - Jan Hudec
5
内容准确,这是一个很好的解决方案,用于查找包含多次的标题。然而,原问题没有得到解答,我无法想象在什么情况下这会是一个好主意。Cpp文件应该包含其所依赖的所有头文件,即使这个头文件在其他地方已经被包含过了。您不希望您的项目受到编译顺序的影响或者假定另一个头文件将包含您需要的头文件。 - jaypb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接