前言
Google 风格指南包括前向声明的缺点列表
-
前向声明可以隐藏依赖项,允许用户代码在标头更改时跳过必要的重新编译。
-
前向声明可能会因对库的后续更改而中断。函数和模板的前向声明可以防止标头所有者对其 API 进行其他兼容的更改,例如扩大参数类型、添加具有默认值的模板参数或迁移到新的命名空间。
-
从命名空间 std:: 向前声明符号会产生未定义的行为。
-
可能很难确定是否需要前瞻声明或完整 #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*(。
从标头中向前声明多个符号可能比简单地 #includeing 标头更冗长。
结构化代码以启用前向声明(例如,使用指针成员而不是对象成员(会使代码更慢、更复杂。
问题
我对第一点特别感兴趣,因为我无法提出一个单一的方案,即当标头更改时,前向递减将跳过必要的重新编译。谁能告诉我这是怎么发生的?或者这是谷歌代码库固有的东西?
由于这是清单上的第一点,因此似乎也相当重要。
我无法想出一个单一的方案,即当标头更改时,前向声明将跳过必要的重新编译。
我认为这也有点不清楚,也许可以说得更清楚一点。
隐藏依赖关系意味着什么?
假设您的文件main.cc
需要header.h
才能正确构建。
-
如果
main.cc
包含header.h
,那么这是一个直接依赖关系。 -
如果
main.cc
包含lib.h
,然后lib.h
包含header.h
,那么这是一个间接依赖。 -
如果
main.cc
以某种方式依赖于lib.h
但在不包含lib.h
时不会生成构建错误,那么我可能会将其称为隐藏依赖项。
但是,我不认为隐藏这个词是一个司空见惯的术语,所以我同意措辞可以改进或扩展。
这是怎么发生的?
我有main.c
、lib.h
和types.h
。
这是main.c
:
#include "lib.h"
void test(D* x) { f(x); }
这是lib.h
:
#include "types.h"
void f(B*);
void f(void*);
这是types.h
:
struct B {};
struct D : B {};
现在,main.cc
依赖于types.h
才能生成正确的代码。但是main.cc
只对lib.h
有直接依赖,对types.h
有隐性依赖。如果我在lib.h
中使用前向声明,那么这会破坏main.cc
。然而main.cc
仍然编译!
而main.cc
中断的原因是因为它不包括types.h
,即使main.cc
依赖于types.h
中的声明。前向声明使这成为可能。
我对第一点特别感兴趣,因为我无法提出一个单一的方案,即当标头更改时,前向递减将跳过必要的重新编译。谁能告诉我这是怎么发生的?
发生这种情况是因为依赖关系跟踪器无法推断出如果您使用类的前向声明,定义类的头文件中的某些内容发生了变化。但是,在大多数情况下,这本质上并没有什么错误。
或者这是谷歌代码库固有的东西?
关于D
发布的代码块是有意义的。如果您不#include
定义D
的标头,而仅提供前向声明,则对f(x)
的调用将解析为f(void*)
,这不是您想要的。
IMO,避免前向声明以支持#include
头文件是考虑到上述用例而付出的非常昂贵的代价。但是,如果您有足够的硬件/软件资源,以至于#include
头文件的成本不是一个因素,我可以看到如何证明这样的建议是合理的。