我正在使用Visual Studio 2008开发一个庞大的C++项目,其中有许多不必要的#include
指令。有时候这些#include
只是遗留问题,如果将它们移除,所有文件都能够成功编译;在其他情况下,类可以被前向声明,而#include
指令可以移到对应的.cpp
文件中。是否有一些好用的工具可以检测到这两种情况?
我正在使用Visual Studio 2008开发一个庞大的C++项目,其中有许多不必要的#include
指令。有时候这些#include
只是遗留问题,如果将它们移除,所有文件都能够成功编译;在其他情况下,类可以被前向声明,而#include
指令可以移到对应的.cpp
文件中。是否有一些好用的工具可以检测到这两种情况?
虽然它不会显示不需要的包含文件,但Visual Studio有一个设置/showIncludes
(右键单击.cpp
文件,选择属性>C/C++>高级
),可以在编译时输出所有已包含文件的树形结构。 这可以帮助识别不应该被包含的文件。
您还可以查看pimpl习语,让您少依赖头文件,以便更容易地看到您可以删除的无用信息。
PC Lint非常适合这项工作,并且它还可以为您找到各种其他的问题。 它具有命令行选项,可用于在Visual Studio中创建外部工具,但我发现Visual Lint插件更易于使用。 即使是Visual Lint的免费版本也很有帮助。 但请尝试一下PC-Lint。 调整其配置以避免给出太多警告需要一些时间,但是您会惊讶于它所发现的问题。
!!免责声明!! 我在一款商业静态分析工具(非PC Lint)上工作。!!免责声明!!
简单的非语法解析方法存在以下几个问题:
1) 函数重载集合:
可能会出现重载函数具有来自不同文件的声明。删除其中一个头文件可能导致选择不同的重载函数而不是编译错误!结果是语义上的默默改变,这可能非常难以追踪。
2) 模板特化:
与重载示例类似,如果模板有部分或完全特化,当使用模板时需要将它们全部可见。主模板的特化可能位于不同的头文件中。移除具有特化的头文件不会导致编译错误,但如果该特化被选择,则可能导致未定义的行为。(参考: C++函数模板特化的可见性)
正如“msalters”所指出的那样,对代码进行全面分析还允许分析类的使用情况。通过检查类在文件路径的特定路径上的使用方式,可以完全删除类的定义(因此也删除了所有依赖项),或者至少将其移动到更接近包含树的主源的级别。
我不知道有任何这样的工具,过去我曾考虑编写一个,但问题在于这是一个难以解决的问题。
假设你的源文件包含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
才被定义,因此工具需要知道编译器本身生成哪些指令,以及哪些指令是在编译命令中而不是在头文件中指定的。
和Timmermans一样,我不熟悉任何此类工具。但我认识的程序员会编写Perl(或Python)脚本,尝试逐个注释每个include行,然后编译每个文件。
看起来现在Eric Raymond 有一个这样的工具。
Google的cpplint.py有一个“使用什么就包含什么”的规则(还有许多其他规则),但据我所知,没有“仅包含你使用的内容”。即便如此,它仍然很有用。
试试Include Manager,它可以轻松集成到Visual Studio中,并可视化您的包含路径,帮助您找到不必要的内容。内部使用Graphviz,但还有许多更酷的功能。虽然它是商业产品,但价格非常低廉。
如果您对这个主题有兴趣,您可能想查看Lakos的大规模C++软件设计。它有点过时,但涵盖了许多“物理设计”问题,如找到需要包含的头文件的绝对最小值。我真的没有在其他任何地方看到过这种讨论。
#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#endif
#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#else
#pragma message("Someheader.h superfluously included")
#endif
由于编译器输出正在编译的cpp文件的名称,这将至少让您知道是哪个cpp文件导致了多次引入头文件。
A.h
和B.h
这两个文件,而它们都依赖于C.h
,那么你会包含两次C.h
,但这没问题,因为编译器会在第二次跳过它。如果不这样做,你就必须记住总是先包含C.h
再包含A.h
或B.h
,这样会导致更多无用的包含。 - Jan Hudec