Should one use forward declarations instead of includes wherever possible?

C++Forward Declaration

C++ Problem Overview


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?

C++ Solutions


Solution 1 - C++

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)

Solution 2 - C++

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.

Solution 3 - C++

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

Solution 4 - C++

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.

Solution 5 - C++

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

Solution 6 - C++

> 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

Solution 7 - C++

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

Solution 8 - C++

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

Solution 9 - C++

> 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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMatView Question on Stackoverflow
Solution 1 - C++Luchian GrigoreView Answer on Stackoverflow
Solution 2 - C++Alok SaveView Answer on Stackoverflow
Solution 3 - C++Matthieu M.View Answer on Stackoverflow
Solution 4 - C++anatolygView Answer on Stackoverflow
Solution 5 - C++cmourgliaView Answer on Stackoverflow
Solution 6 - C++Blue DemonView Answer on Stackoverflow
Solution 7 - C++Adrian McCarthyView Answer on Stackoverflow
Solution 8 - C++ks1322View Answer on Stackoverflow
Solution 9 - C++EdwardView Answer on Stackoverflow