如何在 C 或 C++ 项目中维护 #include 语句?似乎几乎不可避免的是,最终文件中的包含语句集要么不够(但由于项目的当前状态而恰好有效),要么包含不再需要的内容。

您是否创建了任何工具来发现或纠正问题?有什么建议么?

我一直在考虑编写一些东西来单独编译每个非头文件多次,每次都删除 #include 语句。继续执行此操作,直到获得最小的包含集。

为了验证头文件是否包含它们需要的所有内容,我将创建一个源文件,它所做的只是包含头文件并尝试编译它。如果编译失败,则说明头文件本身缺少包含。

在我创造一些东西之前,我想我应该在这里问。这似乎是一个普遍存在的问题。

有帮助吗?

解决方案

为了验证头文件是否包含它们需要的所有内容,我将创建一个源文件,它所做的只是包含头文件并尝试编译它。如果编译失败,则说明头文件本身缺少包含。

通过制定以下规则可以获得相同的效果:第一个头文件 .c 或 .cpp 必须包含相应名称的 。H。这样做可以确保 .h 包含编译所需的所有内容。

此外,拉科斯的书 大型C++软件设计 (例如)列出了许多将实现细节从标头移出并移入相应 CPP 文件的技术。如果您将其发挥到极致,使用 Cheshire Cat(隐藏所有实现细节)和 Factory(隐藏子类的存在)等技术,那么许多标头将能够独立存在,而不包含其他标头,而只需使用相反,将声明转发到不透明类型...也许模板类除外。

最后,每个头文件可能需要包含:

  • 数据成员类型没有头文件(相反,数据成员使用“cheshire cat”又名定义/隐藏在 CPP 文件中)“粉刺”技术)

  • 作为方法的参数或返回类型的类型没有头文件(相反,这些是预定义类型,例如 int;或者,如果它们是用户定义的类型,那么它们是引用,在这种情况下,前向声明的、不透明的类型声明就像仅仅 class Foo; 代替 #include "foo.h" 在头文件中就足够了)。

然后你需要的是头文件:

  • 超类,如果这是子类

  • 可能用作方法参数和/或返回类型的任何模板化类型:显然,您也应该能够前向声明模板类,但是某些编译器实现可能存在问题(尽管您也可以封装任何模板,例如 List<X> 作为用户定义类型的实现细节,例如 ListX).

在实践中,我可能会制作一个“standard.h”,其中包含所有系统文件(例如STL 标头、操作系统特定类型和/或任何 #defines等)由项目中的任何/所有头文件使用,并将其作为每个应用程序头文件中的第一个头文件(并告诉编译器将此“standard.h”视为“预编译头文件”) 。


//contents of foo.h
#ifndef INC_FOO_H //or #pragma once
#define INC_FOO_H

#include "standard.h"
class Foo
{
public: //methods
  ... Foo-specific methods here ...
private: //data
  struct Impl;
  Impl* m_impl;
};
#endif//INC_FOO_H

//contents of foo.cpp
#include "foo.h"
#include "bar.h"
Foo::Foo()
{
  m_impl = new Impl();
}
struct Foo::Impl
{
  Bar m_bar;
  ... etc ...
};
... etc ...

其他提示

我习惯将我的包括从高抽象级别排序到低抽象级别。这要求标头必须是自给自足的,并且隐藏的依赖关系会很快显示为编译器错误。

例如,“俄罗斯方块”类有一个Tetris.h和Tetris.cpp文件。 Tetris.cpp的包含顺序是

#include "Tetris.h"     // corresponding header first
#include "Block.h"      // ..then application level includes
#include "Utils/Grid.h" // ..then library dependencies
#include <vector>       // ..then stl
#include <windows.h>    // ..then system includes

现在我意识到这并没有真正回答你的问题,因为这个系统并没有真正帮助清理不需要的包含。好吧..

已经在这个问题中讨论了检测多余包含。

我不知道有任何工具可以帮助检测不足但发生在工作中的内容,但良好的编码约定可以在这里提供帮助。例如, Google C ++风格指南规定了以下内容,目标是减少隐藏的依赖关系:

dir/foo.cc中,其主要目的是实现或测试dir2/foo2.h中的内容,请按以下方式订购包含:

  1. <=>(首选位置<!>#8212;请参阅下面的详细信息。)
  2. C系统文件。
  3. C ++系统文件。
  4. 其他图书馆的.h文件。
  5. 您项目的.h文件。

根据项目的大小,查看由 doxygen 创建的包含图表(带<代码> INCLUDE_GRAPH 选项可能会有所帮助。

删除标头和重新编译技术的一个大问题是它可能导致仍然编译,但错误或低效的代码。

  1. 模板特化:如果您对一个标题中的特定类型具有模板特化,而另一个标题中具有更通用的模板,则删除特化可能会使代码处于可编译状态,但会产生不良结果。

  2. 重载解决方案:一个类似的问题 - 如果你在不同的标题中有一个函数的两个重载但是需要稍微兼容的类型,你可以最终删除一个更适合一个案例的版本,但仍然让代码编译。这可能不如模板专业化版本,但它是可能的。

我一直在考虑编写一些单独编译每个非头部文件的内容,每次删除#include语句。继续执行此操作,直到达到最少的包含。

我认为这是误导,并且会导致“不足但恰好有效”的包含集。

假设您的源文件使用 numeric_limits, ,但还包括一些头文件,由于其自​​身原因包括 <limits>. 。这并不意味着您的源文件不应包含 <limits>. 。其他头文件可能没有记录来定义中定义的所有内容 <limits>, ,恰好如此。有一天它可能会停止:也许它只使用一个值作为某个函数的默认参数,并且也许该默认值从 std::numeric_limits<T>::min() 至 0。现在你的源文件不再编译,并且该头文件的维护者甚至不知道你的文件存在,直到它破坏了他的构建。

除非您现在遇到严重的构建问题,否则我认为删除冗余包含的最佳方法就是养成每当您触摸文件进行维护时都查看列表的习惯。如果您发现有数十个包含文件,并且在查看文件后仍然无法弄清楚每个包含文件的用途,请考虑分解为更小的文件。

如果使用Visual Studio编译器,可以尝试/ showIncludes编译器选项,然后解析它向stderr发出的内容。 MSDN: “使编译器输出包含文件的列表。还会显示嵌套的包含文件(包含在您包含的文件中的文件)。“

烨。我们有自己的预处理器,可以让我们访问自己的宏语言。它还会检查头文件是否只包含一次。创建一个简单的预处理器来检查多个包含应该相当容易。

就工具而言,我已经在Windows上使用了Imagix(这是大约6年前)来识别不需要的包含以及包含哪些包含但通过其他包含间接包含的包含。

看看 清洁程序 项目。虽然他们还没有实现该功能,但计划完成。

来自项目现场:

CPPCLEAN试图在C ++源中找到缓慢开发的问题,尤其是在大型代码库中。它与 lint 类似;但是,CPPCLEAN专注于寻找全球模块间问题,而不是类似于其他静态分析工具的局部问题。

目的是找到在随着时间的时间进行修改的大型代码库中缓慢开发的问题,而未使用的代码。该代码可以从未使用的功能,方法,数据成员,类型等到不必要的#include指令中以多种形式产生。不必要的#crudes会导致大量额外的编译,从而增加编辑编译运行周期。

尤其是 #include 功能:

  • (计划)查找不必要的头文件#included
    • 没有直接引用标题中的任何内容
    • 如果类是前向声明的,则不需要标头
  • (计划)不直接#included引用标头的源文件,即依赖于另一个标头的传递性#include的文件

这里 你可以在 BitBucket 上找到镜像。

如果您使用CDT在Eclipse中编码,则可以使用“组织包含”命令。只需按Ctrl + Shift + O即可添加必要的包含并删除不需要的包含。

我通常创建一个源文件(例如main.c)和一个源文件(main.h)的头文件。在源文件中,我把所有主要类型的“接口”放在我在该文件中使用的函数(在main中,它是 main()),然后在重构这些函数(实现细节)后得到的任何函数,请转到下面。在头文件中,我声明了一些 extern 函数,这些函数在其他源文件中定义,但在使用该头文件的源文件中使用。然后我声明我在该源文件中使用的任何结构或其他数据类型。

然后我只是将它们编译并链接在一起。它保持干净整洁。典型的include ...部分,在我当前的项目中看起来像这样

#include<windows.h>
#include<windowsx.h>
#include<stdio.h>

#include"interface.h"
#include"thissourcefile.h"

//function prototypes

//source

有一个接口标题,用于跟踪我在项目中所有表单中使用的数据结构,然后是 thissourcefile.h ,这正是我所做的解释(声明 externs 等)。

另外,我从不在我的标题中定义任何内容,我只在那里放置声明。这样,它们可以包含在不同的源文件中,并且仍然可以成功链接。函数原型(extern,static或其他)和声明都在标题中,这样它们可以使用很多次 - 定义在源代码中,因为它们只需要在一个地方。

如果您正在创建库或类似的东西,这显然会有所不同。但只是对于内部项目链接,我发现这可以保持一切美观和干净。另外,如果你编写一个makefile,(或者你只是使用IDE),那么编译非常简单有效。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top