Josh Kelley
Josh Kelley

Reputation: 58342

Managing forward declarations

It's well known that using forward declarations is preferable to using #includes in header files, but what's the best way to manage forward declarations?

For a while, I was manually adding to each header file the forward declarations that were needed by that header file. However, I ended up with a bunch of header files repeating the same half-dozen or so forward declarations, which seems redundant, and maintaining these repeated lists got to be a bit tedious.

Forward declarations of typedefs (e.g., struct SensorRecordId; typedef std::vector<SensorRecordId> SensorRecordIdList;) is also a bit much to duplicate across multiple header files.

So then I made a ProjectForwards.h file that contains all of my forward declarations and included that wherever it was needed. At first, this seemed like a good idea - much less redundancy, and much easier maintenance of typedefs. But now, as a result of using ProjectForwards.h so heavily, whenever I add a new class to it, I have to rebuild the world, which slows development.

So what's the best way to manage forward declarations? Should I bite the bullet and repeat individual forward declarations across multiple subsystems? Continue with the ProjectForwards.h approach? Try to split ProjectForwards.h into several SubsystemForwards.h files? Some other solution I'm overlooking?

Upvotes: 32

Views: 5023

Answers (9)

Avi Cohen
Avi Cohen

Reputation: 3414

There is no escaping forward declaration where they are needed.
In your model, If each of your objects from one type communicate with other objects of another type using interfaces only then you will minimize the amount of forward declaration to interfaces only.
If you use templates then you can put your typedefs of them in the precompiled header file.

Upvotes: 0

Tod
Tod

Reputation: 8232

Having done a lot of brown field maintenance I've never been fond of includes that do nothing but include other files or have forward declarations. I prefer to just have them in the header file. You can reduce the typing with the use of templates if your tools support them. You could write a template that expands into your desired text. I would probably include something to make it stand out like

///Begin Forwarding
...
///End Forwarding

That would make it easy to grab and replace if you change the template. If you're more comfortable with tools like grep you could automate the updating from a command line. It would probably be simple to write a script that would update all files, or only the files passed in on the command line. Just a thought.

Upvotes: 3

BЈовић
BЈовић

Reputation: 64203

I was manually adding to each header file the forward declarations that were needed by that header file.

This is the only good way.

Also, if you have a typedef somewhere, it is better to somehow mask it. For example, instead of using a typedef like this :

typedef std::vector< MyClass > MyClassArray;

do this instead :

struct MyClassArray
{
  std::vector< MyClass > t;
};

The bad thing is that you will not be able to use operators, so this will not always work. For example, if you have

typedef std::string MyString;

then it is better to go with typedef.

So then I made a ProjectForwards.h file that contains all of my forward declarations and included that wherever it was needed.

As you discovered, this is a very bad idea. Whenever you modify this header, you'll trigger the recompilation of all files that include it (directly or indirectly).

Upvotes: 0

justin
justin

Reputation: 104698

Here's what I generally do:

It's well known that using forward declarations is preferable to using #includes in header files, but what's the best way to manage forward declarations?

  • Library: Provide a dedicated client forward: (e.g. #include "MONThread/include.fwd.hpp"). Keep Libraries focused (small-ish), and make implementations private where possible.

  • Executable: Forward declare on demand, unless it comes from a library -- always use the library's forward include. Recognize what should be a library (logical or physical) -- many forwards suggest this, as patterns will emerge. Also try to isolate what can be hidden in the process. With libraries and executables, there should be some use of package private types -- these types do not belong in the client's forward headers.

So then I made a ProjectForwards.h file that contains all of my forward declarations and included that wherever it was needed. At first, this seemed like a good idea - much less redundancy, and much easier maintenance of typedefs. But now, as a result of using ProjectForwards.h so heavily, whenever I add a new class to it, I have to rebuild the world, which slows development.

Usually, that means too many large libraries are visible in high levels of the include graph. An ideal include graph (of a large system) is much wider than it is tall -- including what it needs with minimal excess. If every TU needs a few 100,000 lines, you're beyond a problem -- start removing large libraries from high levels.

If that really sounds unsatisfactory, analyze your program's dependencies.

  • Many people make the mistake (in larger projects) of including a ton of large libraries for convenience (e.g. in the pch), which results in recompiling the world (and the pch).

  • Evaluate your dependencies from time to time -- set some soft sensible limits for line count of preprocessor output.

  • The forward headers replace local forward declarations. They do not (generally) belong in the pch.

Upvotes: 1

rodrigo
rodrigo

Reputation: 98338

I personally only include in the global ProjectForwards.h the declarations that are truly global to all, or mostly all, the program. It could also include other files that are almost always needed, for example:

#include <string>
#include <vector>
#include <boost/shared_ptr.hpp>

std::string get_installation_dir();
//...

That way this file rarely changes and there is not need to often rebuilds.

Also, if this file includes a bunch of standard headers, it would be a perfect candidate to be a pre-compiled header!

Upvotes: 0

Dan O
Dan O

Reputation: 4450

I've never seen a "header of forward declares" that was actually useful (noone uses it), didn't quickly become stale (full of stuff that noone uses), and wasn't an iteration bottleneck (touched the forward declare header? recompile everything!). Generally they develop all three problems.

The core of your problem is system design. These subsystems you've mentioned should probably be including the header files that define the types they need to take as input or output. By breaking types that are being used by multiple subsystems into their own header file you'll strike a nice balance between isolation and efficient interop between subsystems.

Upvotes: 3

herzbube
herzbube

Reputation: 13378

I don't think there is a single "best" solution, each has its own advantages and drawbacks. Even though it's more work, I personally favor the "each header file has its own forward declarations" approach, for the following reasons:

  • It's as lean as it can get: No additional files that need to be found and parsed.
  • No obfuscation: Just by looking at the header file you see exactly which types it needs.
  • No unnecessary namespace pollution. If you collect forward declarations in a ProjectForwards.h file, that file will contain the sum of all declarations needed by all of its consumers. So if only a single consumer needs a certain declaration, all the others will inherit it, too.

If these arguments are not convincing, maybe because they are too puristic :-), then I would suggest following the middle way of splitting ProjectForwards.h.

Upvotes: 2

Michael Kristofik
Michael Kristofik

Reputation: 35178

It sounds like these classes are fairly common to much of your project. You might try some of these:

  • Do your best to break apart ProjectForwards.h into several files as you suggested. Make sure each subsystem only gets the declarations it truly needs. If nothing else, that process will force you to think about the coupling between your subsystems and you might find ways to reduce it. These are all good steps toward avoiding over-compilation.

  • Mimic <iosfwd>. Have each common class or module provide its own forward-include header that just provides the class names and any convenience typedefs. Then you can #include that everywhere. Yes, you'll repeat the list a lot, but think about it this way: nobody complains about #including <vector>, <string>, and <map> in six different places in their code.

  • Use Pimpl more often. This will have a similar effect to my previous suggestion but will require more work on your part. If your interfaces are stable, then you can safely provide the typedefs in those headers and #include them directly.

Upvotes: 8

CashCow
CashCow

Reputation: 31435

In general:

  1. Have a forwards file for users of your module. This will only declare those classes that appear as part of the API.

  2. If you have commonly used forwards in your implementation you can have an implementation-only based forwards file.

  3. You probably don't need a forward declaration for every class you use.

Upvotes: 7

Related Questions