应该尽可能使用转发声明而不是 include 吗?

每当一个类声明只使用另一个类作为指针时,使用类前向声明而不包含头文件是否有意义,这样可以先发制人地避免循环依赖的问题?所以,与其拥有:

//file C.h
#include "A.h"
#include "B.h"


class C{
A* a;
B b;
...
};

相反,应该这样做:

//file C.h
#include "B.h"


class A;


class C{
A* a;
B b;
...
};




//file C.cpp
#include "C.h"
#include "A.h"
...

有什么理由不在任何可能的地方这么做吗?

40571 次浏览

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 include A.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)

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 Idiom thus providing a means of hiding implementation from the interface.

However, Forward declaring a class makes that particular class an Incomplete type and 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.

With Incomplete type you can:

  • 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:

  • Use it as a base class.
  • Use it to declare a member.
  • Define functions or methods using this type.

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.

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.

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.

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.

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:

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

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:

#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++ -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.

Running the program:

> ./a.out
truncated=abcd, forgotten="♀♥♂☺☻"
accessing data[1270][1023]
Segmentation fault

Fun fact, in its C++ styleguide, Google recommands using #include everywhere but to avoid circular dependencies.

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, and

  • may lose out on inline optimization.

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, and

  • requires knowing the correct attributes (perhaps it has special alignment requirements).

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.

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.