Should one use forward declarations instead of includes wherever possible?
Asked Answered
V

9

99

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?

Vale answered 28/3, 2012 at 11:19 Comment(6)
uhm - is that the answer to the question on the top or on the bottom?Vale
your real question (at the bottom) - AFAIK there is no reason not to use a forward declaration in this case...Ugaritic
It slightly depends what you mean by "uses another class only as pointers". There's a nasty case where you can delete a pointer using only a forward declaration, but if the class in fact has a non-trivial destructor then you get UB. So if delete "only uses pointers" then yes, there's a reason. If it doesn't count, not so much.Sideman
Isn't the circular dependancy still there and just hidden from the compiler? If yes both tactics always including and always doing forward declaration don't teach you how to avoid circular dependencies. However I must admit that with forward declarations they may be easier to find.Photima
If class is A actually defined as a struct, some compilers may complain. If class A is a derived class, you've got problems. If class A is defined in another namespace and the header just pulls it into this one with a using declaration, you may have problems. If class A is actually an alias (or a macro!), you've got problems. If class A is actually a typedef, you've got problems. If class A is actually a class template with default template parameters, you've got problems. Yes, there is a reason not to forward declare: it breaks encapsulation of implementation details.Tomas
Related: softwareengineering.stackexchange.com/q/388952/322162Isomagnetic
H
75

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)

Henrik answered 28/3, 2012 at 11:21 Comment(10)
Also, increased chance of recompilation.Monarda
"I can't think of a situation where including a file where you can use a forward declaration is better" - when the forward declaration yields UB, see my comment on the main question. You're right to be cautious, I think :-)Sideman
@SteveJessop I didn't know you could do that. It does yield a warning though. Why don't you add this as an answer?Henrik
@Luchian: Because whether it's an answer or not depends what the questioner originally meant, I don't want to post "answers" that amount to quibbling. Maybe the questioner would never dream of writing a delete statement in C.h.Sideman
A downside is more work and more code! And more fragility. You can't possibly say there are no downsides.Feed
@Feed where is more work? what's more code? Is class A; more than #include "A"?Henrik
What if there are 5 classes? What if you later need to add some? You just focused on the best possible case for your point.Feed
@Feed if you need to add more, you'll also need to include the headers. This is a downside for both approaches, but it's related to how the language works, so it's irrelevant.Henrik
I can think of some cases that can become problematic: Forward declaring types that might have started as a struct and later change to class, this can trigger warnings in some static analysers. Forward declaring types that use templates such as typedef std::vector<MyType>; if the template type is to be changhed later, it requires changing all the forward declarations.Shing
But I have to include in cpp file any way since I can't use incomplete type for functions, does forward declaration in header file still increase compilation time?Coraciiform
E
46

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.
Ette answered 28/3, 2012 at 11:21 Comment(4)
"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." Well yes, but if you can forward declare it, it means you don't need a complete type in the header. And if you do need a complete type in a file that includes that header, just include the header for the type you need. IMO, that's an advantage - it forces you to include whatever you need in your implementation file, and not rely on it being included somewhere else.Henrik
Say someone changes that header, and replaces the include with a forward declaration. Then you'd have to go and change all files that included that header, use the missing type but don't include the missing type's header themselves (although they should).Henrik
@LuchianGrigore: ..but if you can forward declare it..., You will have to try it to check out.So there is no fixed rule to just go for Forward Declarations & not include headers, knowing the rules help organzing your implementations.Most common use of Forward declarations is to break circular dependencies & that is where What you can't do with Incomplete types usually bites you.Each source file & header file should contain all the headers it requires for compilation so the second argument does not apply,It is simply a badly organized code to begin with.Ette
for PIMPL it might also make sense to use that forward declarations only in the private portions of your classPhotima
R
24

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.

Ronn answered 28/3, 2012 at 12:28 Comment(2)
This is the only answer pointing out that it has a productivity cost doing this. +1Feed
From a user's perspective. If you forward declare everything it means the user can't just include that file and start working right away. They have to go figure out what the dependencies are (probably due to compiler complaining about an incomplete types) and also include those files before they can start using your class(es). An alternative is to create a "shared.hpp" file or something for your library where all headers are in that file (like boost mentioned above). They can easily include it without figuring out why they can't just "include and go".Lorri
I
14

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.

Isomagnetic answered 28/3, 2012 at 15:28 Comment(1)
+1 for ruining the consensus that the forward declaration is strictly always better :-) IIRC the same problem occurs with types that are "secretly" template instantiations, via typedefs. namespace std { class string; } would be wrong even if it were permitted to put the class declaration in namespace std, because (I think) you can't legally forward-declare a typedef as if it were a class.Sideman
B
12

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

Benefit answered 30/12, 2016 at 17:11 Comment(1)
See also this SO question and its answers: #36597381Legate
M
9

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
Morehead answered 23/6, 2014 at 18:0 Comment(0)
T
6

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.

Tomas answered 30/12, 2016 at 17:48 Comment(3)
At the end you recommend using pimpl idiom, but the entire usefulness of that idiom is based on forward declarations. Forward declarations and pimpl idiom are pretty much the same thing.Sp
@user2445507: The question was "whenever possible." My point is that it's usually not a good idea to do "whenever possible." As I said in my penultimate paragraph, it's fine for the owner of the interface to provide forwarding declarations, since they can keep the forward declarations in sync with the actual interface. With the pimpl idiom, the same programmer is responsible for the forward declaration and impl object's implementation, so that's perfectly fine.Tomas
Your original statement is a bit ambiguous. It could be interpreted to mean that pimpl doesn't use forward declarations (ie. they are another option). I've ran into this misconception before with people suggesting to use pimpl INSTEAD of fwd declarations, not understanding the contradiction. Anyways, thanks for clarifying.Sp
L
2

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.

Lenard answered 28/3, 2012 at 11:36 Comment(1)
@Luchian Grigore: probably it's ok for some simple test programLenard
S
-1

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

Sinistrorse answered 27/6, 2013 at 12:25 Comment(2)
You're answering a different question ("Should I use pointers?"), not the one that was asked ("When I'm using pointers only, is there any reason not to use forward declarations?").Schaeffer
@AndrewMedico i think this is a good answer, pointing out the performance drawback, which did answer the question. "forward declaration == using pointer"Interknit

© 2022 - 2024 — McMap. All rights reserved.