清理 Visual Studio 中的 C/C++ include

从 Visual Studio 17.8 预览版 1 开始,Visual Studio 提供了一项 #include 清理功能,可通过以下方式提高代码的质量:

  • 为编译目的只是由于其他头文件间接包含了所需头文件的代码,添加头文件。
  • 移除未使用的头文件 - 提高编译时间和代码整洁度。

默认情况下,Include Cleanup 处于打开状态。 若要了解如何配置它,请参阅在 Visual Studio 中配置 C/C++ Include Cleanup

直接标头与间接标头

首先,让我们了解一些术语:

  • 直接标头是在代码中显式 #include 的标头。
  • 间接标头是隐式 #include 的标头。 而直接包含的头文件会包含标头。 换言之,间接标头通过 transitively 方式包含。

Include Cleanup 会分析代码并确定哪些标头未使用,以及哪些标头是间接包含的。 请考虑以下头文件:

// myHeader.h

#include <string>
#include <iostream>

void myFunc()
{
    std::string s = "myFunc()\n";
    std::cout << s;
}

使用它的程序:

// myProgram.cpp
#include "myHeader.h"

int main()
{
    std::string s = "main()"; // string is indirectly included by myHeader.h
    std::cout << s; // cout is indirectly included by myHeader.h
    myFunc();
}

myHeader.h 是直接标头,因为 myProgram.cpp 显式包含它。 myHeader.h 包含 <string><iostream>,因此这些标头是间接标头。

问题是 myProgram.cpp 使用 std::stringstd::cout,但不直接包含定义它们的标头。 此代码恰好可以进行编译,因为 myHeader.h 包含这些标头。 此代码很容易出错,因为如果 myHeader.h 已停止包括任一标头,myProgram.cpp 将无法再编译。

根据 C++ 准则,最好为所有依赖项显式包含标头,以便代码不会因头文件更改而出错。 有关详细信息,请参阅 C++ Core Guidelines SF.10

Include Cleanup 会对代码进行分析,以识别未使用和间接包含的标头。 它基于在 Visual Studio 中配置 C++ #include 工具中所述的设置提供反馈。 反馈可以采用错误列表警告、建议等形式。有关 Include Cleanup 提供的反馈的更多详细信息,请参阅 Include Cleanup 消息

未使用的标头

随着代码的演变,你可能不再需要一些头文件。 这很难在复杂的项目中进行跟踪。 随着时间的推移,编译可能需要更长的时间,因为编译器将处理不必要的头文件。 Include Cleanup 可帮助你查找和移除未使用的标头。 例如,如果在 myProgram.cpp 中注释掉了 myFunc() 会怎样:

// myProgram.cpp
#include "myHeader.h"

int main()
{
    std::string s = "main()"; // string is indirectly included from myHeader.h
    std::cout << s; // cout is indirectly included from myHeader.h
    // myFunc(); // directly included from myHeader.h
}

在以下屏幕截图中,#include "myHeader.h" 呈灰显状态(在 Visual Studio 中配置 C++ #include 工具中对该设置进行了介绍),因为它在 myFunc() 被注释掉后将不再使用。

将光标悬停在灰显的 #include 上以显示快速操作菜单。 单击灯泡(或选择“显示潜在修复”链接),查看与未使用的文件相关的操作:

Three refactoring options are shown: Remove # include myHeader.h, remove all unused includes, and Add all transitively used and remove all unused # includes.

添加可传递使用的标头

我们可以选择移除未使用的头文件,但这会破坏代码,因为 <string><iostream> 是通过 myheader.h 间接包含的。

我们可以改为选择“添加所有可传递使用的 #include,并移除所有未使用的 #include”。 这会移除未使用的标头 myHeader.h,但还会添加通过 myHeader.h 间接包含的任何标头。 在本例中,结果是将 #include <string>#include <iostream> 添加到 myProgram.cpp,并移除 #include "myHeader.h"

// myProgram.cpp
#include <iostream>
#include <string>

int main()
{
    std::string s = "main()"; // string is directly included from <string>
    std::cout << s; // cout is directly included from <string>
    // MyFunc();
}

该工具不会更新注释,但可以看到代码现在正在直接使用 std::stringstd::cout。 此代码不再容易出错,因为它不依赖于 myHeader.h 来包含其他必需的标头。

最佳做法

在未首先添加间接包含的头文件的情况下,请勿移除看似未使用的头文件。 这是因为您的代码可能依赖于头文件中的间接 include,而该头文件在其他情况下是不使用的。 首先添加可传递使用的标头。 然后,在移除未使用的标头时,就不会因为缺少已移除的头文件间接包含的头文件而出现编译错误。

执行此操作的一种方法是将 Include Cleanup 中针对“添加缺失的 include 的建议级别”的设置设定为“建议”(“工具”>“选项”>“文本编辑器”>“C/C++”>“代码清理”)。 此外,还需要将“移除未使用的 include 的建议级别”设置为“建议”。 然后:

  1. 在错误列表中,确保筛选器设置为“编译 + IntelliSense”。
  2. 查找“此文件中使用了来自 #include x 的内容并可传递包含”的实例。
  3. 将光标悬停在带有建议的行上。 从灯泡下拉列表中,选择“添加所有可传递使用的 include”。
  4. 重复项目中的这些步骤,直到解决有关可传递 include 的所有建议。
  5. 移除未使用的 include:在错误列表中,查找“#include x 未在此文件中使用”的实例。
  6. 将光标悬停在未使用的标头上。 从灯泡下拉列表中,选择“移除所有未使用的 include”。
  7. 重复项目中的这些步骤,直到解决所有 Include Cleanup 建议。

在此简要概述中,你已了解 Include Cleanup 如何帮助移除未使用的标头及添加间接包含的标头。 这有助于保持代码整洁,可以提高编译速度,减少代码出错几率。

另请参阅

在 Visual Studio 中配置 C/C++ Include Cleanup
Include Cleanup 消息