C++ 应该尽可能使用前向声明而不是包含吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9906402/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Should one use forward declarations instead of includes wherever possible?
提问by Mat
Whenever a class declaration uses another class only as pointers, does it make sense to use a class forward declaration instead of including the headerfile in order to pre-emptively avoid problems with circular dependencies? so, instead of having:
每当一个类声明仅使用另一个类作为指针时,使用类前向声明而不是包含头文件以预先避免循环依赖问题是否有意义?所以,而不是:
//file C.h
#include "A.h"
#include "B.h"
class C{
A* a;
B b;
...
};
do this instead:
这样做:
//file C.h
#include "B.h"
class A;
class C{
A* a;
B b;
...
};
//file C.cpp
#include "C.h"
#include "A.h"
...
Is there any reason why not to do this wherever possible?
有什么理由不尽可能这样做吗?
采纳答案by Luchian Grigore
The forward-declaration method is almost always better. (I can't think of a situation where including a file where you can use a forward declaration is better, but I'm not gonna say it's always better just in case).
前向声明方法几乎总是更好。(我想不出包含一个可以使用前向声明的文件更好的情况,但我不会说它总是更好以防万一)。
There are no downsides to forward-declaring classes, but I can think of some downsides for including headers unnecessarily:
前向声明类没有缺点,但我可以想到不必要地包含标题的一些缺点:
longer compilation time, since all translation units including
C.h
will also includeA.h
, although they might not need it.possibly including other headers you don't need indirectly
polluting the translation unit with symbols you don't need
you might need to recompile source files that include that header if it changes (@PeterWood)
编译时间更长,因为所有翻译单元包括
C.h
也将包括A.h
,尽管他们可能不需要它。可能包括您间接不需要的其他标题
用不需要的符号污染翻译单元
如果它发生变化,您可能需要重新编译包含该标头的源文件(@PeterWood)
回答by Alok Save
Yes, using forward declarations is always better.
是的,使用前向声明总是更好。
Some of the advantages they provide are:
它们提供的一些优点是:
- Reduced compilation time.
- No namespace pollute.
- (In some cases)may reduce the size of your generated binaries.
- Recompilation time can be significantly reduced.
- Avoiding potential clash of preprocessor names.
- Implementing PIMPL Idiomthus providing a means of hiding implementation from the interface.
- 减少编译时间。
- 没有命名空间污染。
- (在某些情况下)可能会减少生成的二进制文件的大小。
- 重新编译时间可以显着减少。
- 避免预处理器名称的潜在冲突。
- 实现PIMPL Idiom从而提供了一种从接口隐藏实现的方法。
However, Forward declaring a class makes that particular class an Incomplete typeand that severely, restricts what operations you can perform on the Incomplete type.
You cannot perform any operations which would need the compiler to know the layout of the class.
但是,Forward 声明一个类会使该特定类成为Incomplete 类型,这严重限制了您可以对 Incomplete 类型执行的操作。
您不能执行任何需要编译器知道类布局的操作。
With Incomplete type you can:
使用 Incomplete 类型,您可以:
- Declare a member to be a pointer or a reference to the incomplete type.
- Declare functions or methods which accepts/return incomplete types.
- Define functions or methods which accepts/return pointers/references to the incomplete type (but without using its members).
- 将成员声明为指向不完整类型的指针或引用。
- 声明接受/返回不完整类型的函数或方法。
- 定义接受/返回指向不完整类型的指针/引用的函数或方法(但不使用其成员)。
With Incomplete type you cannot:
使用 Incomplete 类型,您不能:
- Use it as a base class.
- Use it to declare a member.
- Define functions or methods using this type.
- 将其用作基类。
- 用它来声明一个成员。
- 使用此类型定义函数或方法。
回答by Matthieu M.
Is there any reason why not to do this wherever possible?
有什么理由不尽可能这样做吗?
Convenience.
方便。
If you know ahead of phase that any user of this header file will necessarily need to also include the definition of A
to do anything (or perhaps most of the times). Then it is convenient to just include it once and for all.
如果你事先知道这个头文件的任何用户都必须包括A
做任何事情的定义(或者大部分时间)。然后,一劳永逸地包含它很方便。
This is a rather touchy subject, as a too liberal use of this rule of thumbs will yield a nigh uncompilable code. Note that Boost approaches the problem differently by providing specific "convenience" headers which bundles a couple of close functionalities together.
这是一个相当敏感的主题,因为过于随意地使用这个经验法则会产生几乎无法编译的代码。请注意,Boost 通过提供特定的“便利”标头来以不同的方式解决问题,这些标头将几个紧密的功能捆绑在一起。
回答by anatolyg
One case in which you don't want to have forward declarations is when they are themselves tricky. This can happen if some of your classes are templated, like in the following example:
您不希望有前向声明的一种情况是它们本身很棘手。如果您的某些类是模板化的,则可能会发生这种情况,如下例所示:
// Forward declarations
template <typename A> class Frobnicator;
template <typename A, typename B, typename C = Frobnicator<A> > class Gibberer;
// Alternative: more clear to the reader; more stable code
#include "Gibberer.h"
// Declare a function that does something with a pointer
int do_stuff(Gibberer<int, float>*);
Forward-declarations are the same as code duplication: if the code tends to change a lot, you have to change it in 2 places or more each time, and that is no good.
前向声明与代码重复相同:如果代码往往更改很多,则每次必须在 2 个或更多地方更改它,这是不好的。
回答by Blue Demon
Should one use forward declarations instead of includes wherever possible?
应该尽可能使用前向声明而不是包含吗?
No, explicit forward declarations should not be considered as a general guideline. Forward declarations are essentially copy and pasted, or misspelled code, which in case you find a bug in it, need to fixed everywhere the forward declarations are used. This can be error-prone.
不,不应将明确的前向声明视为一般准则。前向声明本质上是复制和粘贴或拼写错误的代码,如果您发现其中的错误,则需要在使用前向声明的任何地方进行修复。这可能容易出错。
To avoid mismatches between the "forward" declarations and its definitions, put declarations in a header file and include that header file in both the defining and the declaration-using source files.
为避免“前向”声明与其定义之间的不匹配,请将声明放在头文件中,并将该头文件包含在定义和使用声明的源文件中。
In this special case, however, where only an opaque class is forward declared, this forward declaration may be okay to use, but in general, to "use forward declarations instead of includes whenever possible", like the title of this thread says, can be quite risky.
然而,在这种特殊情况下,只有一个不透明的类被前向声明,这个前向声明可能可以使用,但一般来说,“尽可能使用前向声明而不是包含”,就像这个线程的标题所说的那样,可以相当冒险。
Here are some examples of "invisible risks" concerning forward declarations (invisible risks = declaration mismatches that are not detected by the compiler or linker):
以下是一些关于前向声明的“隐形风险”示例(隐形风险 = 编译器或链接器未检测到的声明不匹配):
Explicit forward declarations of symbols representing data may be unsafe, because such forward declarations might require correct knowledge of the footprint (size) of the data type.
Explicit forward declarations of symbols representing functions may also be unsafe, like the parameter types and the number of parameters.
表示数据的符号的显式前向声明可能是不安全的,因为此类前向声明可能需要正确了解数据类型的足迹(大小)。
表示函数的符号的显式前向声明也可能不安全,例如参数类型和参数数量。
The example below illustrates this, e.g., two dangerous forward declarations of data as well as of a function:
下面的示例说明了这一点,例如,数据和函数的两个危险的前向声明:
File a.c:
文件 ac:
#include <iostream>
char data[128][1024];
extern "C" void function(short truncated, const char* forgotten) {
std::cout << "truncated=" << std::hex << truncated
<< ", forgotten=\"" << forgotten << "\"\n";
}
File b.c:
文件公元前:
#include <iostream>
extern char data[1280][1024]; // 1st dimension one decade too large
extern "C" void function(int tooLarge); // Wrong 1st type, omitted 2nd param
int main() {
function(0x1234abcd); // In worst case: - No crash!
std::cout << "accessing data[1270][1023]\n";
return (int) data[1270][1023]; // In best case: - Boom !!!!
}
Compiling the program with g++ 4.7.1:
使用 g++ 4.7.1 编译程序:
> g++ -Wall -pedantic -ansi a.c b.c
Note: Invisible danger, since g++ gives no compiler or linker errors/warnings
Note: Omitting extern "C"
leads to a linking error for function()
due to the c++ name mangling.
注意:不可见的危险,因为 g++ 没有给出编译器或链接器错误/警告
注意:由于 c++ 名称修改,省略extern "C"
会导致链接错误function()
。
Running the program:
运行程序:
> ./a.out
truncated=abcd, forgotten="♀?♂??"
accessing data[1270][1023]
Segmentation fault
回答by Zouch
Fun fact, in its C++ styleguide, Google recommands using #include
everywhere but to avoid circular dependencies.
有趣的事实是,在其 C++ styleguide 中,Google 建议#include
在任何地方使用,但要避免循环依赖。
回答by Adrian McCarthy
Is there any reason why not to do this wherever possible?
有什么理由不尽可能这样做吗?
Absolutely: It breaks encapsulation by requiring the user of a class or function to know and duplicate implementation details. If those implementation details change, code that forward declares can be broken while code that relies on the header will continue to work.
绝对:它通过要求类或函数的用户知道和复制实现细节来打破封装。如果这些实现细节发生变化,转发声明的代码可能会被破坏,而依赖于标头的代码将继续工作。
Forward declaring a function:
向前声明一个函数:
requires knowing that it's implemented as a function and not an instance of a static functor object or (gasp!) a macro,
requires duplicating the default values for default parameters,
requires knowing its actual name and namespace, since it may just be a
using
declaration that pulls it into another namespace, perhaps under an alias, andmay lose out on inline optimization.
需要知道它是作为函数实现的,而不是静态函子对象的实例或(喘气!)宏,
需要复制默认参数的默认值,
需要知道它的实际名称和命名空间,因为它可能只是一个
using
将它拉入另一个命名空间的声明,也许在别名下,并且可能会失去内联优化。
If the consuming code relies on the header, then all those implementation details can be changed by the function provider without breaking your code.
如果消费代码依赖于标头,那么函数提供者可以更改所有这些实现细节,而不会破坏您的代码。
Forward declaring a class:
向前声明一个类:
requires knowing whether it's a derived class and the base class(es) it's derived from,
requires knowing that it's a class and not just a typedef or a particular instantiation of a class template (or knowing that it is a class template and getting all the template parameters and default values correct),
requires knowing the true name and namespace of the class, since it may be a
using
declaration that pulls it into another namespace, perhaps under an alias, andrequires knowing the correct attributes (perhaps it has special alignment requirements).
需要知道它是否是派生类和派生自的基类,
需要知道它是一个类,而不仅仅是一个 typedef 或一个类模板的特定实例(或者知道它是一个类模板并获得所有模板参数和默认值正确),
需要知道类的真实名称和命名空间,因为它可能是一个
using
将它拉入另一个命名空间的声明,可能是在别名下,并且需要知道正确的属性(也许它有特殊的对齐要求)。
Again, forward declaring breaks the encapsulation of these implementation details, making your code more fragile.
同样,前向声明破坏了这些实现细节的封装,使您的代码更加脆弱。
If you need to cut header dependencies to speed up compilation time, then get the provider of the class/function/library to provide a special forward declarations header. The standard library does this with <iosfwd>
. This model preserves the encapsulation of implementation details and gives the library maintainer the ability to change those implementation details without breaking your code, all while reducing the load on the compiler.
如果你需要减少头文件依赖以加快编译时间,那么获取类/函数/库的提供者来提供一个特殊的前向声明头文件。标准库使用<iosfwd>
. 该模型保留了实现细节的封装,并使库维护者能够在不破坏代码的情况下更改这些实现细节,同时减少编译器的负载。
Another option is to use a pimpl idiom, which hides implementation details even better and speeds up compiles at the cost of a small run-time overhead.
另一种选择是使用 pimpl idiom,它可以更好地隐藏实现细节,并以较小的运行时开销为代价加快编译速度。
回答by ks1322
Is there any reason why not to do this wherever possible?
有什么理由不尽可能这样做吗?
The only reason I think of is to save some typing.
我想到的唯一原因是节省一些打字。
Without forward declarations you can include header file just once, but I don't advice to do so on any rather big projects due to disadvantages pointed by other people.
如果没有前向声明,您可以只包含一次头文件,但由于其他人指出的缺点,我不建议在任何相当大的项目中这样做。
回答by Edward
Is there any reason why not to do this wherever possible?
有什么理由不尽可能这样做吗?
Yes - Performance. Class objects are stored with their data members together in memory. When you use pointers, the memory to the actual object pointed to is stored elsewhere on the heap, usually far away. This means accessing that object will cause a cache miss and reload. This can make a big difference in situations where performance is crucial.
是 - 性能。类对象与其数据成员一起存储在内存中。当您使用指针时,指向实际对象的内存存储在堆上的其他地方,通常很远。这意味着访问该对象将导致缓存未命中并重新加载。在性能至关重要的情况下,这可能会产生很大的不同。
On my PC the Faster() function runs approx 2000x faster than the Slower() function:
在我的 PC 上,Faster() 函数的运行速度比 Slower() 函数快大约 2000 倍:
class SomeClass
{
public:
void DoSomething()
{
val++;
}
private:
int val;
};
class UsesPointers
{
public:
UsesPointers() {a = new SomeClass;}
~UsesPointers() {delete a; a = 0;}
SomeClass * a;
};
class NonPointers
{
public:
SomeClass a;
};
#define ARRAY_SIZE 100000
void Slower()
{
UsesPointers list[ARRAY_SIZE];
for (int i = 0; i < ARRAY_SIZE; i++)
{
list[i].a->DoSomething();
}
}
void Faster()
{
NonPointers list[ARRAY_SIZE];
for (int i = 0; i < ARRAY_SIZE; i++)
{
list[i].a.DoSomething();
}
}
class SomeClass
{
public:
void DoSomething()
{
val++;
}
private:
int val;
};
class UsesPointers
{
public:
UsesPointers() {a = new SomeClass;}
~UsesPointers() {delete a; a = 0;}
SomeClass * a;
};
class NonPointers
{
public:
SomeClass a;
};
#define ARRAY_SIZE 100000
void Slower()
{
UsesPointers list[ARRAY_SIZE];
for (int i = 0; i < ARRAY_SIZE; i++)
{
list[i].a->DoSomething();
}
}
void Faster()
{
NonPointers list[ARRAY_SIZE];
for (int i = 0; i < ARRAY_SIZE; i++)
{
list[i].a.DoSomething();
}
}
In parts of applications which are performance-critical or when working on hardware which is especially prone to cache coherence problems, data layout and usage can make a huge difference.
在对性能至关重要的部分应用程序或在特别容易出现缓存一致性问题的硬件上工作时,数据布局和使用可能会产生巨大的差异。
This is a good presentation on the subject and other performance factors: http://research.scee.net/files/presentations/gcapaustralia09/Pitfalls_of_Object_Oriented_Programming_GCAP_09.pdf
这是关于该主题和其他性能因素的很好的介绍:http: //research.scee.net/files/presentations/gcapaustralia09/Pitfalls_of_Object_Oriented_Programming_GCAP_09.pdf