11

我有一堆需要编写单元测试的遗留代码。它在任何地方都使用预编译的头文件,因此几乎所有的 .cpp 文件都依赖于 stdafx.h,这使得为了编写测试而打破依赖关系变得困难。

我的第一直觉是删除所有这些 stdafx.h 文件,这些文件在大多数情况下包含#include 指令,并根据需要将这些 #include 直接放在源文件中。

这将使得有必要关闭预编译的头文件,因为它们依赖于像 stdafx.h 这样的文件来确定预编译的头文件停止的位置。

有没有办法在没有 stdafx.h 依赖项的情况下保留预编译的头文件?有没有更好的方法来解决这个问题?

4

9 回答 9

9

是的,有更好的方法。

恕我直言,预编译头文件的“向导风格”的问题是它们鼓励不必要的耦合并使重用代码比应有的更难。此外,使用“将所有内容都保留在 stdafx.h”样式编写的代码很容易维护,因为更改任何头文件中的任何内容都可能导致整个代码库每次都重新编译。这可以使简单的重构花费很长时间,因为每次更改和重新编译周期都比它应该花费的时间长得多。

恕我直言,更好的方法是使用#pragma hdrstop 和/Yc 和/Yu。这使您能够轻松设置使用预编译头文件的构建配置,也可以构建不使用预编译头文件的配置。使用预编译头文件的文件不直接依赖源文件中的预编译头文件本身,这使得它们可以使用或不使用预编译头文件进行构建。项目文件确定哪个源文件构建预编译头文件,每个源文件中的#pragma hdrstop 行确定哪些包含来自预编译头文件(如果使用),哪些直接来自源文件......这意味着当做维护你会使用不' t 使用预编译的头文件,只有在头文件更改后需要重新构建的代码才会重新构建。在进行完整构建时,您可以使用预编译的头文件配置来加快编译过程。使用非预编译头文件构建选项的另一个好处是,它确保您的 cpp 文件只包含他们需要的内容并包含他们需要的所有内容(如果您使用预编译头文件的“向导样式”,这将很难做到。

我在这里写了一些关于它是如何工作的:http: //www.lenholgate.com/blog/2004/07/fi-stlport-precompiled-headers-warning-level-4-and-pragma-hdrstop.html(忽略关于 /FI 的东西),我在这里有一些使用 #pragma hdrstop 和 /Yc /Yu 方法构建的示例项目:http: //www.lenholgate.com/blog/2008/04/practical-testing-16- --fixing-a-timeout-bug.html

当然,从 'wizard style' 预编译的头文件使用到更受控制的样式通常不是微不足道的......

于 2008-11-15T23:21:40.663 回答
4

当您通常使用预编译头文件时,“stdafx.h”有两个目的。它定义了一组稳定、通用的包含文件。同样在每个 .cpp 文件中,它用作预编译头文件结束位置的标记。

听起来你想要做的是:

  • 保持打开预编译头。
  • 将“stdafx.h”保留在每个 .cpp 文件中。
  • 清空“stdafx.h”中的包含。
  • 对于每个 .cpp 文件,从旧的“stdafx.h”中找出需要哪些包含。在每个 .cpp 文件中的 #include "stdafx.h" 之前添加这些。

所以现在你有了最小的依赖集,你仍然在使用预编译的头文件。损失是您不会只预编译您的公共标头集一次。对于全面重建来说,这将是一个巨大的打击。对于开发模式,您一次只重新编译几个文件,它的影响较小。

于 2008-11-14T14:07:46.367 回答
2

不,可能没有更好的方法。

但是,对于给定的单个 .cpp 文件,您可能会决定不需要预编译的标头。您可以修改该 .cpp 文件的设置并删除 stdafx.h 行。

(但实际上,我不知道预编译的标头方案如何干扰单元测试的编写)。

于 2008-11-14T13:18:19.447 回答
2

不,预编译的头文件依赖于以这种方式编译的所有源包含的单个头文件。您可以为单个源(或全部)指定根本不使用预编译的标头,但这不是您想要的。

过去,Borland C++ 编译器在没有特定头文件的情况下进行预编译。但是,如果两个源文件包含相同的头文件但顺序不同,则它们将分别编译,因为确实,C++ 中头文件的顺序可能很重要......

因此,这意味着 borland 预编译头文件只有在您非常严格地以相同顺序包含源代码或所有其他文件都包含(首先)单个包含文件时才节省时间...... - 听起来很熟悉?!?!

于 2008-11-14T13:18:55.593 回答
2

是的。“stdafx.h/stdafx.pch”名称只是约定俗成的。您可以为每个 .cpp 提供自己的预编译头文件。这可能最容易通过一个小脚本来编辑 .vcproj 中的 XML 来实现。缺点:你最终会得到一大堆预编译的头文件,并且它们不会在 TU 之间共享。

可能,但很聪明?我不能肯定地说。

于 2008-11-14T14:43:47.090 回答
1

我的建议是 - 不要删除预编译的头文件,除非你想让你的构建非常缓慢。您在这里基本上有三个选择:

  1. 摆脱预编译的头文件(不推荐)
  2. 为遗留代码创建一个单独的库;这样你就可以单独构建它。
  3. 在单个项目中使用多个预编译头文件。您可以在解决方案资源管理器中选择单个 C++ 文件,并告诉他们要使用哪个预编译头文件。您还需要设置您的 OtherStdAfx.h/cpp 以生成预编译的标头。
于 2008-11-14T13:19:18.010 回答
1

预编译的标头基于这样一个想法,即所有内容都将包含相同的内容集。如果您想使用预编译的头文件,那么您必须忍受这暗示的依赖关系。它归结为依赖关系与构建速度的权衡。如果您可以在关闭预编译头文件的情况下在合理的时间内构建,那么一定要这样做。

要考虑的另一件事是每个库可以有一个 pch。因此,您可以将代码拆分为更小的库,并让每个库都有更紧密的依赖关系。

于 2008-11-14T14:53:37.743 回答
0

我只对需要包含 afx___ 内容的代码使用预编译的标头 - 通常只是 UI,我不对其进行单元测试。UI 代码处理 UI 并调用具有单元测试的函数(尽管大多数当前没有,因为应用程序是遗留的)。

对于大部分代码,我不使用预编译的头文件。

G。

于 2008-11-14T13:20:19.413 回答
0

预编译头文件可以在重建项目时节省大量时间,但如果预编译头文件发生更改,则依赖于头文件的每个源文件都将重新编译,无论更改是否影响它。幸运的是,预编译的头文件用于编译,而不是链接每个源文件不必使用相同的预编译头文件。

pch1.h:

#include <bigHeader1.h>
#include ...


pch1.cpp:

#include "pch1.h"


源1.cpp:

#include "pch1.h"
[code]


pch2.h:

#include <bigHeader2.h>
#include ...


pch2.cpp:

#include "pch2.h"


源代码2.cpp

#include "pch2.h"
[code]

选择pch1.cpp,右键单击,Properties,Configuration Properties,C/C++,Precompiled Headers
预编译头文件: 创建(/Yc)
预编译头文件: pch1.h
预编译头文件输出文件:$(intDir)pch1.pch

选择source1.cpp
预编译头文件: 使用(/Yu)
预编译头文件: pch1.h
预编译头文件输出文件:$(intDir)pch1.pch(我认为这对 /Yu 无关紧要)

对pch2.cppsource2.cpp做同样的事情,除了将头文件头输出文件设置为pch2.hpch2.pch。这对我行得通。

于 2016-02-20T03:58:56.553 回答