3

I am wondering if there is any drawback for using forward declarations in all places when possible. This is if my header contains only declarations.

As far as I understand, using forward declaration speeds up compile time, but I don't know of any drawbacks as such.

Example:

a.h:

Class A
{
};

b.h:

// Should I use and include "a.h" in the cpp file (e.g., a.cpp)
Class A;
Class B
{
    doSomething(A *a);
    A *myA;
};

Or is it better to use

b.h:

#include "a.h"

Class B
{
    doSomething(A *a);
    A *myA;
};
bashrc
  • 4,725
  • 1
  • 22
  • 49
Anton
  • 1,181
  • 2
  • 17
  • 27
  • 1
    As far as I know, there are no real drawbacks. – Max Feb 27 '12 at 19:31
  • 1
    From the other side, you should use pointers in declarations to make forward declarations work, but using values is better in most cases. – Lol4t0 Feb 27 '12 at 19:33

5 Answers5

10

Using forward declarations improves decoupling. If you can avoid including "A.h" by using a forward declaration, it is a good idea to use forward declaration. It is better not only because your builds run faster (after all, preprocessed headers can deal with compiler efficiency pretty well) but because it tells the readers of your declaration that the structure of your class B does not depend on knowing anything about your class A, other than that it exists*.

EDIT (to answer your question) The only downside to forward declarations that I know is that you cannot use them in all situations: for example, a declaration similar to this:

class B
{
    A myA[10];
};

would not compile, because the compiler needs to know the size of A. However, the compiler finds such issues very reliably, and informs you about them in unambiguous terms.

* The implementation of class B could very well depend on knowing the details of class A. However, this dependency becomes an implementation detail of B hidden from the users of your class; you can change it at any time without breaking the code dependent upon class B.

RastaJedi
  • 641
  • 1
  • 6
  • 18
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    He asked what the *downsides* are. You didn't list any. – Nicol Bolas Feb 27 '12 at 19:39
  • @NicolBolas That's a good point, I edited the answer to address the "cons" side of the argument (admittedly, I did not have much to say about it, other than "I wish I could use it more"). – Sergey Kalinichenko Feb 27 '12 at 19:53
  • Your statement about 'it tells the readers of your declaration that your class B does not depend on knowing anything about your class A, other than that it exists' is wrong in some way. For example it is perfectly fine for B to have a member of type A* and use a FWD in B's header. The implementation of B might then call a method of A in its implementation file (where a FWD would not suffice anymore) and therefore B would depend on more than the mere existence of A. However, if you provide your users only the header files they cannot know about this dependency. – sigy Sep 04 '14 at 13:01
  • @sigy I added a footnote to reflect the clarification from your comment. Thanks! – Sergey Kalinichenko Sep 04 '14 at 13:15
2

Forward declaration is the only way to break the cyclic inclusion.

This is the main drawback when not used carefully, I think. I worked in a large project where forward declarations are made whenever possible. Cyclic dependencies were a real problem in the end.

  • It feels like you're describing an advantage not a disadvantage – Jer Apr 08 '20 at 18:58
  • @Jer Sorry, I guess I didn't make myself clear. There have been dependencies that were not planned and that were not noticed. In the end we had big problems to split the software into different components to compile them separately. If we hadn't used forward declarations extensively, the mutual dependencies would have been noticed earlier. The compiler would have complained. – Christian H. Apr 11 '20 at 11:42
  • I'd be interested in seeing the scenario that caused b/c the purpose of FD is literally to prevent this. It reduces the amount of includes in your headers meaning other files including your header aren't inadvertently depending on the includes it has. Which allows you to identify dependencies much earlier on and prevent scenarios where downstream classes break because you remove an include your class no longer needs. Circular dependencies are a huge code design smell. I guess if someone misuses fwd declarations to create circular dependencies then I can see what you're saying. – Jer Apr 15 '20 at 14:37
2

using forward declaration speeds up compiler time

This is partially true, because the compiler (and preprocessor) do not need to parse included headers in every file you include this header.

The real improvement you see when you change the header and need to recompile.

Forward declaration is the only way to break the cyclic inclusion.

BЈовић
  • 62,405
  • 41
  • 173
  • 273
1

I'll speak in practical terms. Pros:

  1. Avoids circular compiler dependencies. The way you wrote the code above would not even compile otherwise unless you put A and B in the same header.

  2. It avoids compile-time dependencies. You're allowed to change a.h without recompiling units that include b.h. For the same reason, it speeds up builds in general. To find out more on this subject, I recommend looking up the Pimpl idiom.

Cons:

  1. Applied heavily in this way you have above, your general source files will probably need to include more headers (we cannot instantiate or work with A simply by including B.h). To me, that's a worthwhile exchange for faster builds.

  2. This is probably the biggest con which is that it can come with some runtime overhead depending on what you are doing. In the example you gave, B cannot directly store A as a value. It involves a level of indirection, which may also imply an extra heap allocation/deallocation if B is the memory manager of A (the same would be true of a pimpl). Whether this overhead is trivial or not is where you have to draw the line, and it's worth remembering that maintainability and developer productivity is definitely more important than a micro-optimization which won't even be noticeable to the user. I wouldn't use this as a reason to rule out this practice unless it is definitely proving to be a bottleneck or you know well in advance that the cost of a heap allocation/deallocation or pointer indirection is going to be a non-trivial overhead.

stinky472
  • 6,737
  • 28
  • 27
0

The only drawback that comes to mind is that forward declarations require pointers. Therefore they may not be initialized and therefore could cause a null reference exception. As the coding standard that I currently use, requires all pointers require a null reference check if can add allot of code. I started to get around this with a Design By Contract invariants; then I can assert that anything initialized in the constructor never be null.

Burnt Toast
  • 909
  • 5
  • 2