序言
《 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的正向decl,则test()将调用f(void *)。
从头文件中声明多个符号比仅#include头文件更冗长。
构造代码以启用前向声明(例如,使用指针成员而不是对象成员)可以使代码更慢,更复杂。
问题
我对第一点特别感兴趣,因为我无法提出一个单一的方案,即当标头更改时,前向取消会跳过必要的重新编译。谁能告诉我这是怎么发生的?还是这是Google代码库所固有的?
由于这是列表上的第一点,因此似乎也很重要。
答案 0 :(得分:2)
我无法提出一个单一的方案,即当标头更改时,前向声明会跳过必要的重新编译。
我认为这也不太清楚,也许措辞可能更清楚。
隐藏依赖项是什么意思?
假设您的文件main.cc
需要header.h
才能正确构建。
如果main.cc
包含header.h
,则这是直接依赖项。
如果main.cc
包含lib.h
,然后lib.h
包含header.h
,则这是间接依赖。
如果main.cc
某种程度上依赖于lib.h
,但是如果不包含lib.h
却没有生成构建错误,那么我可以称其为 hidden 依赖关系。
我不认为 hidden 这个字眼是常识,但是,我同意可以改进或扩展其措词。
我有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
有 hidden 依赖关系。如果我在lib.h
中使用前向声明,则会中断main.cc
。但是main.cc
仍然可以编译!
main.cc
中断的原因是,即使types.h
取决于main.cc
中的声明,它也不包含types.h
。前向声明使之成为可能。
答案 1 :(得分:0)
我对第一点特别感兴趣,因为我无法提出一个单一的方案,即当标头更改时,前向清理将跳过必要的重新编译。谁能告诉我这是怎么发生的?
之所以会发生,是因为如果使用类的前向声明,则依赖项跟踪器无法推断出定义该类的头文件中的某些内容已更改。但是,在大多数情况下,这没有本质上的错误。
这是Google代码库所固有的吗?
关于D
的已发布代码块很有意义。如果您没有#include
定义D
的标头,而仅提供前向声明,则对f(x)
的调用将解析为f(void*)
,这不是您想要的。
IMO,避免仅使用头文件#include
进行前向声明是一个非常昂贵的代价,只能考虑上述用例。但是,如果您有足够的硬件/软件资源来考虑#include
头文件的开销不是一个因素,那么我可以看到如何证明这样的建议。