为什么Google样式指南不鼓励前向声明?

22

不是说Google Style Guide就是圣经,但作为一个新手程序员,它似乎是个不错的参考。

Google Style Guide列出了以下前向声明的缺点:

  1. 前向声明可能会隐藏依赖性,使用户代码在头文件更改时可以跳过必要的重新编译。

  2. 库的后续更改可能会破坏前向声明。函数和模板的前向声明可能会阻止头文件所有者对其API进行本来兼容的更改,例如扩大参数类型、添加具有默认值的模板参数或迁移到新命名空间。

  3. 从命名空间std::前向声明符号会导致未定义行为。

  4. 很难确定是否需要前向声明或完整的# include。将#include替换为前向声明可能会悄然更改代码的含义:

代码:

  // b.h:
  struct B {};
  struct D : B {};

  // good_user.cc:
  #include "b.h"
  void f(B*);
  void f(void*);
  void test(D* x) { f(x); }  // calls f(B*)
如果将#include替换为B和D的前向声明,test()将调用f(void*)。
5. 从头文件中前向声明多个符号可能比简单地#include头文件更冗长。
6. 构造代码以启用前向声明(例如使用指针成员而不是对象成员)可能使代码变得更加复杂且更慢。
然而,在SO上进行一些搜索似乎表明前向声明普遍是更好的解决方案。
因此,考虑到这些看似非微不足道的缺点,能否有人解释这种差异?
何时可以安全地忽略某些或所有这些缺点?

10
企业风格指南是为了最不熟练的企业代码人员而编写的,请记住这一点。阅读时要牢记这一点。第4点中提供的示例有些牵强附会。一个具有 void* 类型参数的函数是一个有问题的函数。 - Richard Hodges
然而,一些在SO上的搜索似乎表明前向声明是普遍更好的解决方案。【需要引证】 - Mat
16
谷歌风格指南是为谷歌打造的。除非你是谷歌或类似的公司,否则这些规则可能不适用于你。你要记住,谷歌需要维护约1000万行C++代码,因此一些规则(例如“不要使用异常”)是为了维护而制定的,因为重写1000万行代码使其具备异常安全性是不可行的。如果他们从头开始,他们会做出不同的选择。 - nwp
前向声明可能会使分层分析(和模块)变得棘手。 - Kerrek SB
2
与其被视为“良好的参考资料”,GSG(Google编码规范)通常被非Google观察者持怀疑态度。它可能适用于Google,正如@nwp所说,对于那样规模的项目,您需要保持一致性...但是它不应该被视为普通用户的参考资料。 - underscore_d
显示剩余2条评论
2个回答

10
一些关于SO的搜索似乎表明前向声明是普遍更好的解决方案。我不认为这就是SO所说的。您引用的文本是将“游击队”前向声明与包括正确的include文件进行比较。我不认为您会在SO上找到很多支持谷歌在此批评的方法。那种糟糕的方法是,“不,不要#include include文件,只写几个函数和类型的声明来使用。”适当的include文件仍将包含其自身的前向声明,并且SO上的搜索将建议这样做是正确的,因此我知道您从何得到SO支持声明的想法。但Google并不是说库的自己的include文件不应该包含前向声明,而是说你不应该自己为每个要使用的函数或类型编写前向声明。如果您#include正确的include文件,并且构建链工作正常,则依赖性不会隐藏,尽管include文件包含声明,但大部分问题大多数情况下不适用。还有一些困难,但这不是Google在这里讨论的问题。特别是看前向声明与类定义相比的类型,(4)给出了一个出现问题的例子(因为D的前向声明无法表达它是从B派生的,对于这一点,需要类定义)。还有一种称为“Pimpl”的技术,它确实对前向类型声明进行了谨慎使用以实现特定目的。因此,您将在SO上看到一些支持,但这并不意味着支持每个人通常会运行前向声明类而不是#include其头文件的想法。

那么,您对于使用前向声明(尽量减少#include)在.hpp文件中,并在.cpp文件中使用#include的经验法则有何评论(或者没有?)? - Rufus
@Woofas:我所说的一切同样适用于hpp文件和cpp文件。偶尔在hpp文件中,您可能会创建循环依赖关系,在这种情况下,最好通过重新组织文件内容来解决问题,但作为绝对的最后手段,您可以接受所有列出的前向声明的缺点,并使用它们来解决问题。 - Steve Jessop

7

来自Titus Winters的CppCon 2014 talk:

我们最近学到的一个重要教训是:使用模板进行前向声明是一个非常糟糕的想法。这会导致维护问题,你无法想象。在某些情况下,前向声明可能是可以接受的。我的怀疑是规则实际上会改变为:鼓励库所有者提供一个特定的头文件来前向声明他们认为值得的东西(强调添加),你可能不应该自己前向声明,也不应该有人前向声明一个模板类型。我们将看到。我们仍在从我们所学到的[不可听]细节中解决问题。

那么,尝试直接前向声明模板类型可能存在问题,这可能是他们反对全面使用前向声明的动机之一吗?

此外,提供“一个特别声明他们认为值得的事情的头文件”听起来类似于<iosfwd>的使用方式,如这里(解决方案2.2),这里这里所述。

编辑:

明确一下,我并不是说我同意或不同意谷歌反对前向声明。我只是试图理解他们的理由,并对 <iosfwd> 作了一个旁白/观察。

个人而言,我尽可能使用前向声明,遵循上面链接中 GOTW 的指南(解决方案3):

指南:当前向声明足以满足需求时,永远不要 #include 头文件。

但 Winters 的理由似乎也有一些道理。我曾经处理过从第三方库中前向声明模板类型的代码,语法确实变得混乱(我还没有遇到 Winters 所提到的维护问题)。另一方面,我不确定是否应该像谷歌 C++ 风格指南中所述那样阻止所有前向声明,但我想这对谷歌来说是可行的?

免责声明:我不是专家,仍在学习中。


这个建议非常有道理,已经被C++标准库所采纳。有一些头文件提供了模板类型的前向声明(例如iosfwd)。 - Cody Gray
1
由于原帖评论已关闭,我想指出该指南是错误的,或者至少与标准库规则(ISO/IEC 14882中定义)不一致。这很容易导致不符合规范的代码,即使是在cppreference上编辑器也是如此,直到我报告了它,然后它被纠正了 - FrankHB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接