简体   繁体   English

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

[英]Is it worth forward-declaring library classes?

I've just started learning Qt, using their tutorial. 我刚开始学习Qt,使用他们的教程。 I'm currently on tutorial 7, where we've made a new LCDRange class. 我目前正在使用教程7,我们已经制作了一个新的LCDRange类。 The implementation of LCDRange (the .cpp file) uses the Qt QSlider class, so in the .cpp file is LCDRange(.cpp文件)的实现使用Qt QSlider类,因此在.cpp文件中是

#include <QSlider>

but in the header is a forward declaration: 但在标题中是一个前向声明:

class QSlider;

According to Qt, 根据Qt,

This is another classic trick, but one that's much less used often. 这是另一个经典的技巧,但经常使用的技巧要少得多。 Because we don't need QSlider in the interface of the class, only in the implementation, we use a forward declaration of the class in the header file and include the header file for QSlider in the .cpp file. 因为我们在类的接口中不需要QSlider,所以只在实现中,我们在头文件中使用类的前向声明,并在.cpp文件中包含QSlider的头文件。

This makes the compilation of big projects much faster, because the compiler usually spends most of its time parsing header files, not the actual source code. 这使得大项目的编译速度更快,因为编译器通常花费大部分时间来解析头文件,而不是实际的源代码。 This trick alone can often speed up compilations by a factor of two or more. 仅这一技巧通常可以将编辑速度提高两倍或更多。

Is this worth doing? 这值得吗? It seems to make sense, but it's one more thing to keep track of - I feel it would be much simpler just to include everything in the header file. 这似乎是有意义的,但是还有一件事要跟踪 - 我觉得将所有内容都包含在头文件中要简单得多。

Absolutely. 绝对。 The C/C++ build model is ...ahem... an anachronism (to say the best). C / C ++构建模型是... ahem ...一个时代错误(说最好的)。 For large projects it becomes a serious PITA. 对于大型项目,它将成为一个严肃的PITA。

As Neil notes correctly, this should not be the default approach for your class design, don't go out of your way unless you really need to. 正如尼尔正确地指出,这应该是你设计类的默认的态度,不走出自己的路,除非你真的需要。

Breaking Circular include references is the one reason where you have to use forward declarations. 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

Reducing rebuild time - Imagine the following code 减少重建时间 - 想象一下以下代码

// foo.h
#include <qslider>
class Foo
{
   QSlider * someSlider;
}

now every .cpp file that directly or indirectly pulls in Foo.h also pulls in QSlider.h and all of its dependencies. 现在,直接或间接引入Foo.h的每个.cpp文件也会引入QSlider.h及其所有依赖项。 That may be hundreds of .cpp files! 这可能是数百个.cpp文件! (Precompiled headers help a bit - and sometimes a lot - but they turn disk/CPU pressure in memory/disk pressure, and thus are soon hitting the "next" limit) (预编译的头文件有点帮助 - 有时很多 - 但它们会在内存/磁盘压力下转动磁盘/ CPU压力,因此很快达到“下一个”限制)

If the header requires only a reference declaration, this dependency can often be limited to a few files, eg foo.cpp. 如果标头仅需要引用声明,则此依赖关系通常可以限制为几个文件,例如foo.cpp。

Reducing incremental build time - The effect is even more pronounced, when dealing with your own (rather than stable library) headers. 减少增量构建时间 - 在处理您自己的(而不是稳定的库)头时,效果更加明显。 Imagine you have 想象一下,你有

// bar.h
#include "foo.h"
class Bar 
{
   Foo * kungFoo;
   // ...
}

Now if most of your .cpp's need to pull in bar.h, they also indirectly pull in foo.h. 现在,如果你的大部分.cpp需要拉入bar.h,他们也会间接拉入foo.h. Thus, every change of foo.h triggers build of all these .cpp files (which might not even need to know Foo!). 因此,foo.h的每次更改都会触发所有这些.cpp文件的构建(甚至可能不需要知道Foo!)。 If bar.h uses a forward declaration for Foo instead, the dependency on foo.h is limited to bar.cpp: 如果bar.h使用Foo的前向声明,则对foo.h的依赖仅限于bar.cpp:

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

// bar.cpp
#include "bar.h"
#include "foo.h"
// ...

It is so common that it is a pattern - the PIMPL pattern . 它是一种模式 - PIMPL模式 是如此常见 It's use is two-fold: first it provides true interface/implementation isolation, the other is reducing build dependencies. 它的用途是双重的:首先它提供真正的接口/实现隔离,另一个是减少构建依赖性。 In practice, I'd weight their usefulness 50:50. 在实践中,我会以50:50的重量来衡量它们的实用性。

You need a reference in the header, you can't have a direct instantiation of the dependent type. 您需要在标头中引用 ,您不能直接实例化依赖类型。 This limits the cases where forward declarations can be applied. 这限制了可以应用前向声明的情况。 If you do it explicitely, it is common to use a utility class (such as boost::scoped_ptr ) for that. 如果你明确地这样做,那么通常使用实用程序类(例如boost :: scoped_ptr )。

Is Build Time worth it? 构建时间值得吗? Definitely , I'd say. 当然 ,我会说。 In the worst case build time grows polynomial with the number of files in the project. 在最坏的情况下,构建时间会随着项目中的文件数量而增加多项式。 other techniques - like faster machines and parallel builds - can provide only percentage gains. 其他技术 - 如更快的机器和并行构建 - 只能提供百分比增益。

The faster the build, the more often developers test what they did, the more often unit tests run, the faster build breaks can be found fixed, and less often developers end up procrastinating. 构建越快,开发人员测试他们所做的越多,单元测试运行的次数越多,可以找到更快的构建中断,并且开发人员最不会拖延。

In practice, managing your build time, while essential on a large project (say, hundreds of source files), it still makes a "comfort difference" on small projects. 在实践中,管理构建时间虽然对于大型项目(例如,数百个源文件)至关重要,但它仍然会对小型项目产生“舒适的差异”。 Also, adding improvements after the fact is often an exercise in patience, as a single fix might shave off only seconds (or less) of a 40 minute build. 此外,在事实之后添加改进通常是耐心的练习,因为单个修复可能仅在40分钟构建的几秒(或更少)中削减。

I use it all the time. 我用它所有的时间。 My rule is if it doesn't need the header, then i put a forward declaration ( "use headers if you must, use forward declarations if you can" ). 我的规则是,如果它不需要标题,那么我提出了一个前向声明( “如果必须,请使用标题,如果可以,请使用前向声明” )。 The only thing that sucks is that i need to know how the class was declared (struct/class, maybe if it is a template i need its parameters, ...). 唯一糟糕的是我需要知道如何声明类(struct / class,如果它是模板,我需要它的参数,......)。 But in the vast majority of times, it just comes down to "class Slider;" 但在绝大多数时候,它只是归结为"class Slider;" or something along that. 或者其他什么。 If something requires some more hassle to be just declared, one can always declare a special forward declare header like the Standard does with iosfwd too. 如果某些东西需要更多的麻烦才能被宣布,那么总是可以声明一个特殊的前向声明标题,就像标准对iosfwd

Not including the header file will not only reduce compile time but also will avoid polluting the namespace. 不包括头文件不仅会减少编译时间,还会避免污染命名空间。 Files including the header will thank you for including as little as possible so they can keep using a clean environment. 包括标题在内的文件会感谢您尽可能少地包含,以便他们可以继续使用干净的环境。

This is the rough plan: 这是一个粗略的计划:

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

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

...

There are smart pointers that are specifically designed to work with pointers to incomplete types. 有一些智能指针专门设计用于指向不完整类型的指针。 One very well known one is boost::shared_ptr . 一个众所周知的是boost::shared_ptr

Yes, it sure does help. 是的,确实有帮助。 Another thing to add to your repertoire is precompiled headers if you are worried about compilation time. 如果您担心编译时间,那么添加到您的保留曲目的另一件事是预编译头文件。

Look up FAQ 39.12 and 39.13 查找FAQ 39.1239.13

The standard library does this for some of the iostream classes in the standard header <iosfwd> . 标准库对标准头文件<iosfwd>中的某些iostream类执行此操作。 However, it is not a generally applicable technique - notice there are no such headers for the other standard library types, and it should not (IMHO) be your default approach to designing class heirarchies. 但是,它不是一种普遍适用的技术 - 请注意,其他标准库类型没有这样的标题,并且它不应该(IMHO)是您设计类层次结构的默认方法。

Although this eems to be a favourite "optimisation" for programmers, I suspect that like most optimisations, few of them have actually timed the build of their projects both with and without such declarations. 虽然这对于程序员来说是最受欢迎的“优化”,但我怀疑,与大多数优化一样,他们中很少有人在有或没有这样的声明的情况下实际构建他们的项目。 My limited experiments in this area indicate that the use of pre-compiled headers in modern compilers makes it unecessary. 我在这方面的有限实验表明,在现代编译器中使用预编译的头文件使其不必要。

There is a HUGE difference in compile times for larger projects, even ones with carefully managed dependencies. 对于较大的项目,即使是具有谨慎管理的依赖项的项目,编译时间也存在巨大差异。 You better get the habit of forward declaring and keep as much as possible out of header files, because at a lot of software shops which uses C++ it's required. 你最好养成向前声明并尽可能多地保留头文件的习惯,因为在很多使用C ++的软件商店都需要它。 The reason for why you don't see it all that much in the standard header files is because those make heavy use of templates, at which point forward declaring becomes hard. 你在标准头文件中看不到这么多的原因是因为那些大量使用模板,此时声明变得很难。 For MSVC you can use /P to take a look at how the preprocessed file looks before actual compilation. 对于MSVC,您可以使用/ P来查看在实际编译之前预处理文件的外观。 If you haven't done any forward declaration in your project it would probably be an interesting experience to see how much extra processing needs to be done. 如果你没有在你的项目中做过任何前瞻声明,那么看看需要做多少额外的处理可能是一个有趣的经验。

In general, no. 一般来说,没有。

I used to forward declare as much as I could, but no longer. 我曾经尽可能多地向前宣布,但不再。

As far as Qt is concerned, you may notice that there is a <QtGui> include file that will pull in all the GUI Widgets. 就Qt而言,您可能会注意到有一个<QtGui>包含文件将<QtGui>所有GUI小部件。 Also, there is a <QtCore> , <QtWebKit> , <QtNetwork> etc. There's a header file for each module. 此外,还有<QtCore><QtWebKit><QtNetwork>等。每个模块都有一个头文件。 It seems the Qt team believes this is the preferred method also. 似乎Qt团队认为这也是首选方法。 They say so in their module documentation. 他们在模块文档中这么说。

True, the compilation time may be increased. 没错,编译时间可能会增加。 But in my experience its just not that much. 但根据我的经验,它并没有那么多。 And if it were, using precompiled headers would be the next step. 如果是这样,使用预编译头将是下一步。

When you write ... 当你写...

include "foo.h" 包括“foo.h”

... you thereby instruct a conventional build system "Any time there is any change whatsover in the library file foo.h, discard this compilation unit and rebuild it, even if all that happened to foo.h was the addition of a comment, or the addition of a comment to some file which foo.h includes; even if all that happened was some ultra-fastidious colleague re-balanced the curly braces; even if nothing happened other than a pressured colleague checked in foo.h unchanged and inadvertently changed its timestamp." ...从而指示一个传统的构建系统“任何时候在库文件foo.h中有任何改变,丢弃这个编译单元并重建它,即使foo.h发生的所有事情都是添加注释,或者对foo.h包含的某个文件添加注释;即使所有发生的事情都是一些超级挑剔的同事重新平衡花括号;即使没有发生任何其他事情,除了受到压力的同事检查foo.h不变且无意中改变了它的时间戳。“

Why would you want to issue such a command? 你为什么要发出这样的命令? Library headers, because in general they have more human readers than application headers, have a special vulnerability to changes that have no impact on the binary, such as improved documentation of functions and arguments or the bump of a version number or copyright date. 库标题,因为它们通常比应用程序标题具有更多的人类读者,对于对二进制文件没有影响的更改具有特殊的漏洞,例如改进的函数和参数文档或版本号或版权日期的颠覆。

The C++ rules allow namespace to be re-opened at any point in a compilation unit (unlike a struct or class ) in order to support forward declaration. C ++规则允许在编译单元中的任何一点(与结构类不同 )重新打开命名空间 ,以支持前向声明。

Forward declarations are very useful for breaking the circular dependencies, and sometimes may be ok to use with your own code, but using them with library code may break the program on another platform or with other versions of the library (this will happen even with your code if you're not careful enough). 前向声明对于打破循环依赖非常有用,有时可以使用您自己的代码,但将它们与库代码一起使用可能会破坏另一个平台上的程序或其他版本的库(即使你的代码,如果你不够小心)。 IMHO not worth it. 恕我直言不值得。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM