管理前瞻性声明

时间:2012-02-07 16:56:40

标签: c++ forward-declaration

众所周知,使用前向声明比在头文件中使用#includes更好,但是管理前向声明的最佳方法是什么?

有一段时间,我手动向每个头文件添加了该头文件所需的前向声明。但是,我最终得到了一堆头文件,重复了相同的6个左右的前向声明,这似乎是多余的,并且维护这些重复的列表变得有点乏味。

typedef的前向声明(例如,struct SensorRecordId; typedef std::vector<SensorRecordId> SensorRecordIdList;)在多个头文件中复制也有点多。

然后我创建了一个包含所有前向声明的ProjectForwards.h文件,并将其包含在需要的任何地方。起初,这似乎是个好主意 - 更不用说冗余了,而且更容易维护typedef。但是现在,由于大量使用ProjectForwards.h,每当我向它添加一个新类时,我都必须重建世界,这会减慢开发速度。

那么管理前向声明的最佳方法是什么?我应该咬紧牙关并在多个子系统中重复单独的前向声明吗?继续ProjectForwards.h方法?尝试将ProjectForwards.h拆分为多个SubsystemForwards.h个文件?我忽略了一些其他解决方案?

9 个答案:

答案 0 :(得分:7)

听起来这些类对于你的大部分项目都是相当普遍的。您可以尝试其中一些:

  • 尽量按照建议将ProjectForwards.h拆分为多个文件。确保每个子系统只获得它真正需要的声明。如果不出意外,该过程将迫使您考虑子系统之间的耦合,您可能会找到减少它的方法。这些都是避免过度编译的好方法。

  • 模仿<iosfwd>。让每个公共类或模块提供自己的forward-include头,它只提供类名和任何便利typedef。然后你可以#include到处都是。是的,你会重复这个列表,但是这样思考:没有人在他们的代码中的六个不同位置抱怨#including <vector><string><map>。 / p>

  • 更频繁地使用Pimpl。这将与我之前的建议产生类似的效果,但需要您做更多的工作。如果您的接口稳定,那么您可以安全地在这些标头中提供typedef并直接#include它们。

答案 1 :(得分:6)

一般来说:

  1. 为您的模块用户提供转发文件。这只会声明那些作为API一部分出现的类。

  2. 如果您在实施中使用了常用转发器,则可以使用基于实现的转发文件。

  3. 您可能不需要为您使用的每个课程提供前瞻声明。

答案 2 :(得分:3)

做了很多棕色的现场维护,我从来没有喜欢包括除了包含其他文件或有前瞻性声明之外什么都不做。我更喜欢将它们放在头文件中。如果您的工具支持,您可以使用模板减少输入。 您可以编写一个扩展为所需文本的模板。我可能会包含一些东西,使它像

一样突出
///Begin Forwarding
...
///End Forwarding

如果您更改模板,这将使抓取和替换变得容易。如果您对grep等工具更加熟悉,可以从命令行自动更新。编写一个可以更新所有文件的脚本或者只更新命令行传入的文件可能很简单。只是一个想法。

答案 3 :(得分:3)

我从未见过实际上有用的“前方声明标题”(没有人使用它),没有迅速变得陈旧(充满了没人使用的东西),并且不是迭代瓶颈(触及了forward declare header?重新编译一切!)。通常他们会发展所有三个问题。

问题的核心是系统设计。您提到的这些子系统应该包括头文件,这些头文件定义了他们需要作为输入或输出的类型。通过将多个子系统使用的类型分解为它们自己的头文件,您将在隔离和子系统之间的高效互操作之间取得良好的平衡。

答案 4 :(得分:2)

我认为没有一个“最佳”解决方案,每个都有自己的优点和缺点。虽然它的工作量更大,但我个人赞成“每个头文件都有自己的前向声明”方法,原因如下:

  • 它尽可能精简:无需找到和解析其他文件。
  • 没有混淆:只需查看头文件,您就可以确切地看到它需要哪些类型。
  • 没有不必要的命名空间污染。如果您在ProjectForwards.h文件中收集前向声明,则该文件将包含其所有使用者所需的所有声明的总和。因此,如果只有一个消费者需要某个声明,那么其他所有人都会继承它。

如果这些论点不具说服力,可能是因为它们过于纯粹:-),那么我建议按照分裂ProjectForwards.h的中间方式。

答案 5 :(得分:1)

以下是我通常做的事情:

  

众所周知,使用前向声明比在头文件中使用#includes更好,但是管理前向声明的最佳方法是什么?

  • 图书馆:提供专门的客户转发:(例如#include "MONThread/include.fwd.hpp")。保持图书馆的重点(小型),并尽可能将实施设为私有。

  • 可执行文件:按需转发声明,除非它来自库 - 始终使用库的转发包含。认识到什么应该是一个库(逻辑或物理) - 许多前锋建议这样,因为模式将出现。还尝试隔离过程中可隐藏的内容。对于库和可执行文件,应该使用包私有类型 - 这些类型不属于客户端的转发头。

  

然后我创建了一个包含所有前向声明的ProjectForwards.h文件,并将其包含在需要的任何地方。起初,这似乎是个好主意 - 更不用说冗余了,而且更容易维护typedef。但是现在,由于大量使用ProjectForwards.h,每当我向它添加一个新类时,我都必须重建世界,这会减慢开发速度。

通常,这意味着在包含图的高级别中可以看到太多的大型库。一个理想的包括(大型系统的)图形比它高得多 - 包括它所需要的最小的过量。如果每个TU需要几十万行,那么你就会遇到问题 - 开始从高级别删除大型库。

如果这听起来不太令人满意,请分析您的程序的依赖性。

  • 许多人为了方便而错误地(在较大的项目中)包含了大量的大型库(例如在pch中),这导致重新编译世界(和pch)。

  • 不时评估您的依赖关系 - 为预处理器输出的行数设置一些软的合理限制。

  • 转发标头取代了本地转发声明。它们(通常)不属于pch。

答案 6 :(得分:0)

我个人只在全球ProjectForwards.h中包含对所有或大部分全部程序真正全球化的声明。它还可以包含几乎总是需要的其他文件,例如:

#include <string>
#include <vector>
#include <boost/shared_ptr.hpp>

std::string get_installation_dir();
//...

这样,这个文件很少会改变,也不需要经常重建。

此外,如果此文件包含一堆标准标题,那么它将成为预编译标题的完美候选者!

答案 7 :(得分:0)

  

我手动向每个头文件添加了该头文件所需的前向声明。

这是唯一的好方法。

另外,如果你在某处有一个typedef,最好以某种方式掩盖它。例如,而不是使用这样的typedef:

typedef std::vector< MyClass > MyClassArray;

改为:

struct MyClassArray
{
  std::vector< MyClass > t;
};

糟糕的是,您将无法使用运营商,因此这并不总是有效。例如,如果你有

typedef std::string MyString;

然后最好使用typedef。

  

然后我创建了一个包含所有前向声明的ProjectForwards.h文件,并将其包含在需要的地方。

正如您所发现的,这是一个非常糟糕的主意。无论何时修改此标头,您都将触发重新编译包含它的所有文件(直接或间接)。

答案 8 :(得分:0)

在需要的地方没有逃避前瞻声明 在您的模型中,如果来自一种类型的每个对象仅使用接口与另一种类型的其他对象进行通信,那么您将最小化前向声明到接口的数量。
如果使用模板,则可以将它们的typedef放在预编译的头文件中。