我正在 Visual Studio 2008 中处理一个大型 C++ 项目,并且有很多包含不必要#include
指令的文件。有时#include
s 只是工件,删除它们后一切都可以正常编译,而在其他情况下,可以向前声明类,并且可以将 #include 移动到.cpp
文件中。有没有什么好的工具可以检测这两种情况?
20 回答
虽然它不会显示不需要的包含文件,但 Visual Studio 有一个设置/showIncludes
(右键单击.cpp
文件,Properties->C/C++->Advanced
),它将在编译时输出所有包含文件的树。这有助于识别不需要包含的文件。
您还可以查看 pimpl idiom 以减少头文件依赖项,从而更轻松地查看可以删除的内容。
PC Lint可以很好地解决这个问题,它还可以为您发现各种其他愚蠢的问题。它具有可用于在 Visual Studio 中创建外部工具的命令行选项,但我发现Visual Lint插件更易于使用。甚至 Visual Lint 的免费版本也有帮助。但是给 PC-Lint 一个机会。配置它以便它不会给你太多警告需要一些时间,但你会惊讶于它的出现。
有一个新的基于 Clang 的工具include-what-you-use旨在做到这一点。
!!免责声明!我使用商业静态分析工具(不是 PC Lint)。!!免责声明!
简单的非解析方法存在几个问题:
1) 过载集:
重载函数的声明可能来自不同的文件。可能是删除一个头文件会导致选择不同的重载,而不是编译错误!结果将是语义上的静默变化,之后可能很难追踪。
2) 模板特化:
与重载示例类似,如果您对模板有部分或显式特化,则希望在使用模板时它们全部可见。可能是主模板的特化位于不同的头文件中。使用特化删除标头不会导致编译错误,但如果选择了特化,则可能导致未定义的行为。(参见:C++ 函数模板特化的可见性)
正如“msalters”所指出的,对代码进行全面分析还可以分析类的使用情况。通过检查如何通过特定的文件路径使用类,可以完全删除类的定义(以及因此它的所有依赖项),或者至少将其移动到更接近包含中主要源的级别树。
不知道有没有这样的工具,以前也想过写一个,结果发现这是一个很难解决的问题。
假设你的源文件包含 ah 和 bh;ah contains#define USE_FEATURE_X
和 bh 使用#ifdef USE_FEATURE_X
. 如果#include "a.h"
被注释掉,您的文件可能仍然可以编译,但可能无法达到您的预期。以编程方式检测这一点并非易事。
无论使用什么工具,这都需要了解您的构建环境。如果 ah 看起来像:
#if defined( WINNT )
#define USE_FEATURE_X
#endif
ThenUSE_FEATURE_X
仅在定义时才定义WINNT
,因此该工具需要知道编译器本身生成哪些指令以及在编译命令中而不是在头文件中指定哪些指令。
像 Timmermans 一样,我不熟悉任何工具。但我认识的程序员写了一个 Perl(或 Python)脚本来尝试一次注释掉每个包含行,然后编译每个文件。
看来,现在 Eric Raymond有一个工具可以做到这一点。
谷歌的cpplint.py有一个“包括你使用的”规则(在许多其他规则中),但据我所知,没有“只包括你使用的”。即便如此,它也很有用。
如果您一般对这个主题感兴趣,您可能想查看 Lakos 的大型 C++ 软件设计。它有点过时了,但涉及到许多“物理设计”问题,例如找到需要包含的绝对最小标题。我还没有在其他任何地方真正看到过这样的讨论。
试试包含管理器。它可以轻松集成到 Visual Studio 中,并可视化您的包含路径,从而帮助您找到不必要的东西。它在内部使用 Graphviz,但还有更多很酷的功能。虽然它是一种商业产品,但它的价格非常低。
您可以使用C/C++ Include File Dependencies Watcher构建包含图,并直观地查找不需要的包含。
如果您的头文件通常以
#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#endif
(而不是使用 #pragma 一次)您可以将其更改为:
#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#else
#pragma message("Someheader.h superfluously included")
#endif
而且由于编译器会输出正在编译的 cpp 文件的名称,这至少会让您知道哪个 cpp 文件导致头被多次引入。
PC-Lint 确实可以做到这一点。一种简单的方法是将其配置为仅检测未使用的包含文件并忽略所有其他问题。这非常简单——只启用消息 766(“模块中未使用的头文件”),只需在命令行中包含选项 -w0 +e766。
同样的方法也可以用于相关消息,例如 964(“模块中未直接使用的头文件”)和 966(“模块中未使用的间接包含的头文件”)。
FWIW 我上周在http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318的博客文章中更详细地写了这个。
添加以下一项或两项#defines 将排除通常不必要的头文件,并且可能会大大缩短编译时间,尤其是在不使用 Windows API 函数的代码时。
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
如果您希望删除不必要#include
的文件以减少构建时间,您的时间和金钱可能最好花在使用cl.exe /MP、make -j、Xoreax IncrediBuild、 distcc/ icecream等并行构建过程上。
当然,如果您已经有一个并行构建过程并且您仍在尝试加快它,那么请务必清理您的#include
指令并删除那些不必要的依赖项。
从每个包含文件开始,并确保每个包含文件只包含编译自身所需的内容。C++ 文件中缺少的任何包含文件都可以添加到 C++ 文件本身中。
对于每个包含和源文件,一次注释掉每个包含文件并查看它是否可以编译。
按字母顺序对包含文件进行排序也是一个好主意,如果无法做到这一点,请添加注释。
如果您还没有,使用预编译的头文件来包含您不会更改的所有内容(平台头文件、外部 SDK 头文件或项目的静态已完成部分)将大大缩短构建时间。
http://msdn.microsoft.com/en-us/library/szfdksca(VS.71).aspx
此外,尽管对您的项目来说可能为时已晚,但将项目组织成多个部分并且不将所有本地标题集中到一个大的主标题中是一种很好的做法,尽管这需要一些额外的工作。
如果您使用 Eclipse CDT,您可以尝试http://includator.com来优化您的包含结构。但是,Includator 可能对 VC++ 的预定义包含不够了解,并且设置 CDT 以使用具有正确包含的 VC++ 尚未内置到 CDT 中。
最新的 Jetbrains IDE CLion 会自动显示(灰色)当前文件中未使用的包含。
也可以从 IDE 中获取所有未使用的包含(以及函数、方法等)的列表。
一些现有的答案表明这很难。确实如此,因为您需要一个完整的编译器来检测适合前向声明的情况。如果不知道符号的含义,就无法解析 C++;语法太模棱两可了。您必须知道某个名称是命名一个类(可以向前声明)还是一个变量(不能)。此外,您需要了解命名空间。
也许有点晚了,但我曾经发现一个 WebKit perl 脚本可以满足您的需求。我相信它需要一些调整(我不精通 perl),但它应该可以解决问题:
(这是一个旧分支,因为主干不再有文件)
如果您认为不再需要某个特定的标头(例如 string.h),您可以注释掉该包含,然后将其放在所有包含的下方:
#ifdef _STRING_H_
# error string.h is included indirectly
#endif
当然,您的接口标头可能使用不同的#define 约定来记录它们在 CPP 内存中的包含。或者没有约定,在这种情况下,这种方法将不起作用。
然后重建。有三种可能:
它构建正常。string.h 不是编译关键的,并且可以删除它的包含。
#error 跳闸。string.g 以某种方式间接包含您仍然不知道是否需要 string.h。如果需要,您应该直接#include 它(见下文)。
你得到一些其他的编译错误。string.h 是必需的,并且没有被间接包含,因此包含一开始是正确的。
请注意,当您的 .h 或 .c 直接使用另一个 .h 时,取决于间接包含几乎可以肯定是一个错误:您实际上承诺您的代码将只需要该标头,只要您使用的其他标头需要它,这可能不是你的意思。
其他答案中提到的关于修改行为的标头而不是声明导致构建失败的内容的警告也适用于此处。