是否值得向前宣布图书馆类?

Ski*_*ick 8 c++ forward-declaration

我刚开始学习Qt,使用他们的教程.我目前正在使用教程7,我们已经制作了一个新的LCDRange类.LCDRange(.cpp文件)的实现使用Qt QSlider类,因此在.cpp文件中是

#include <QSlider>
Run Code Online (Sandbox Code Playgroud)

但在标题中是一个前向声明:

class QSlider;
Run Code Online (Sandbox Code Playgroud)

根据Qt,

这是另一个经典的技巧,但经常使用的技巧要少得多.因为我们没有在类的接口需要QSlider,只有在实施中,我们使用头文件之类的预先声明,并包括在.cpp文件中的QSlider头文件.

这使得大项目的编译速度更快,因为编译器通常花费大部分时间来解析头文件,而不是实际的源代码.仅这一技巧通常可以将编辑速度提高两倍或更多.

这值得吗?这似乎是有意义的,但是还有一件事要跟踪 - 我觉得将所有内容都包含在头文件中要简单得多.

pet*_*hen 17

绝对.C/C++构建模型是... ahem ...一个时代错误(说最好的).对于大型项目,它将成为一个严肃的PITA.

正如尼尔正确地指出,这应该不会成为你的类设计的默认的态度,不走出自己的路,除非你真的需要.

Breaking Circular包括引用是您必须使用前向声明的一个原因.

// a.h
#include "b.h"
struct A { B * a;  }

// b.h
#include "a.h"  // circlular include reference 
struct B { A * a;  }

// Solution: break circular reference by forward delcaration of B or A
Run Code Online (Sandbox Code Playgroud)

减少重建时间 - 想象一下以下代码

// foo.h
#include <qslider>
class Foo
{
   QSlider * someSlider;
}
Run Code Online (Sandbox Code Playgroud)

现在,直接或间接引入Foo.h的每个.cpp文件也会引入QSlider.h及其所有依赖项.这可能是数百个.cpp文件!(预编译的头文件有点帮助 - 有时很多 - 但它们会在内存/磁盘压力下转动磁盘/ CPU压力,因此很快达到"下一个"限制)

如果标头仅需要引用声明,则此依赖关系通常可以限制为几个文件,例如foo.cpp.

减少增量构建时间 - 在处理您自己的(而不是稳定的库)头时,效果更加明显.想象一下,你有

// bar.h
#include "foo.h"
class Bar 
{
   Foo * kungFoo;
   // ...
}
Run Code Online (Sandbox Code Playgroud)

现在,如果你的大部分.cpp需要拉入bar.h,他们也会间接拉入foo.h. 因此,foo.h的每次更改都会触发所有这些.cpp文件的构建(甚至可能不需要知道Foo!).如果bar.h使用Foo的前向声明,则对foo.h的依赖仅限于bar.cpp:

// bar.h
class Foo;
class Bar 
{
   Foo * kungFoo;
   // ...
}

// bar.cpp
#include "bar.h"
#include "foo.h"
// ...
Run Code Online (Sandbox Code Playgroud)

它是一种模式 - PIMPL模式是如此常见.它的用途是双重的:首先它提供真正的接口/实现隔离,另一个是减少构建依赖性.在实践中,我会以50:50的重量来衡量它们的实用性.

您需要在标头中引用,您不能直接实例化依赖类型.这限制了可以应用前向声明的情况.如果你明确地这样做,那么通常使用实用程序类(例如boost :: scoped_ptr).

构建时间值得吗? 当然,我会说.在最坏的情况下,构建时间会随着项目中的文件数量而增加多项式.其他技术 - 如更快的机器和并行构建 - 只能提供百分比增益.

构建越快,开发人员测试他们所做的越多,单元测试运行的次数越多,可以找到更快的构建中断,并且开发人员最不会拖延.

在实践中,管理您的构建时间虽然对于大型项目(例如,数百个源文件)至关重要,但它仍然会对小型项目产生"舒适的差异".此外,在事实之后添加改进通常是耐心的练习,因为单个修复可能仅在40分钟构建的几秒(或更少)中削减.

  • 每个转换单元的构建时间变为O(N),因此整个程序的构建时间变为O(N*N) - 多项式,而不是指数.被动忽略与最坏情况可能是一个常数0 <f <1,所以没有大O差异 (2认同)

Joh*_*itb 8

我用它所有的时间.我的规则是,如果它不需要标题,那么我提出了一个前向声明("如果必须,请使用标题,如果可以,请使用前向声明").唯一糟糕的是我需要知道如何声明类(struct/class,如果它是模板,我需要它的参数,......).但在绝大多数情况下,它只是归结为"class Slider;"或类似的东西.如果某些事情需要更多麻烦才能被宣布,那么总是可以声明一个特殊的前向声明标题,就像标准所做的那样iosfwd.

不包括头文件不仅会减少编译时间,还会避免污染命名空间.包含标题的文件会感谢您尽可能少地包含,以便他们可以继续使用干净的环境.

这是一个粗略的计划:

/* --- --- --- Y.hpp */
class X;
class Y {
    X *x;
};

/* --- --- --- Y.cpp */
#include <x.hpp>
#include <y.hpp>

...
Run Code Online (Sandbox Code Playgroud)

有一些智能指针专门设计用于指向不完整类型的指针.一个众所周知的是boost::shared_ptr.