Header file best practices for typedefs

I'm using shared_ptr and STL extensively in a project, and this is leading to over-long, error-prone types like shared_ptr< vector< shared_ptr<const Foo> > > (I'm an ObjC programmer by preference, where long names are the norm, and still this is way too much.) It would be much clearer, I believe, to consistently call this FooListPtr and documenting the naming convention that "Ptr" means shared_ptr and "List" means vector of shared_ptr.

This is easy to typedef, but it's causing headaches with the headers. I seem to have several options of where to define FooListPtr:

  • Foo.h. That entwines all the headers and creates serious build problems, so it's a non-starter.
  • FooFwd.h ("forward header"). This is what Effective C++ suggests, based on iosfwd.h. It's very consistent, but the overhead of maintaining twice the number of headers seems annoying at best.
  • Common.h (put all of them together into one file). This kills reusability by entwining a lot of unrelated types. You now can't just pick up one object and move it to another project. That's a non-starter.
  • Some kind of fancy #define magic that typedef's if it hasn't already been typedefed. I have an abiding dislike for the preprocessor because I think it makes it hard for new people to grok the code, but maybe....
  • Use a vector subclass rather than a typedef. This seems dangerous...

Are there best practices here? How do they turn out in real code, when reusability, readability and consistency are paramount?

I've marked this community wiki if others want to add additional options for discussion.


Solution 1:

I'm programming on a project which sounds like it uses the common.h method. It works very well for that project.

There is a file called ForwardsDecl.h which is in the pre-compiled header and simply forward-declares all the important classes and necessary typedefs. In this case unique_ptr is used instead of shared_ptr, but the usage should be similar. It looks like this:

// Forward declarations
class ObjectA;
class ObjectB;
class ObjectC;

// List typedefs
typedef std::vector<std::unique_ptr<ObjectA>> ObjectAList;
typedef std::vector<std::unique_ptr<ObjectB>> ObjectBList;
typedef std::vector<std::unique_ptr<ObjectC>> ObjectCList;

This code is accepted by Visual C++ 2010 even though the classes are only forward-declared (the full class definitions are not necessary so there's no need to include each class' header file). I don't know if that's standard and other compilers will require the full class definition, but it's useful that it doesn't: another class (ObjectD) can have an ObjectAList as a member, without needing to include ObjectA.h - this can really help reduce header file dependencies!

Maintenance is not particularly an issue, because the forwards declarations only need to be written once, and any subsequent changes only need to happen in the full declaration in the class' header file (and this will trigger fewer source files to be recompiled due to reduced dependencies).

Finally it appears this can be shared between projects (I haven't tried myself) because even if a project does not actually declare an ObjectA, it doesn't matter because it was only forwards declared and if you don't use it the compiler doesn't care. Therefore the file can contain the names of classes across all projects it's used in, and it doesn't matter if some are missing for a particular project. All that is required is the necessary full declaration header (e.g. ObjectA.h) is included in any source (.cpp) files that actually use them.

Solution 2:

I would go with a combined approach of forward headers and a kind of common.h header that is specific to your project and just includes all the forward declaration headers and any other stuff that is common and lightweight.

You complain about the overhead of maintaining twice the number of headers but I don’t think this should be too much of a problem: the forward headers usually only need to know a very limited number of types (one?), and sometimes not even the full type.

You could even try auto-generating the headers using a script (this is done e.g. in SeqAn) if there are really that many headers.

Solution 3:

+1 for documenting the typedef conventions.

  • Foo.h - can you detail the problems you have with that?
  • FooFwd.h - I'd not use them generally, only on "obvious hotspots". (Yes, "hotspots" are hard to determine). It doesn't change the rules IMO because when you do introduce a fwd header, the associated typedefs from foo.h move there.
  • Common.h - cool for small projects, but doesn't scale, I do agree.
  • Some kind of fancy #define... PLEASE NO!...
  • Use a vector subclass - doesn't make it better. You might use containment, though.

So here the prelimenary suggestions (revised from that other question..)

  1. Standard type headers <boost/shared_ptr.hpp>, <vector> etc. can go into a precompiled header / shared include file for the project. This is not bad. (I personally still include them where needed, but that works in addition to putting them into the PCH.)

  2. If the container is an implementation detail, the typedefs go where the container is declared (e.g. private class members if the container is a private class member)

  3. Associated types (like FooListPtr) go to where Foo is declarated, if the associated type is the primary use of the type. That's almost always true for some types - e.g. shared_ptr.

  4. If Foo gets a separate forward declaration header, and the associated type is ok with that, it moves to the FooFwd.h, too.

  5. If the type is only associated with a particular interface (e.g. parameter for a public method), it goes there.

  6. If the type is shared (and does not meet any of the previous criteria), it gets its own header. Note that this also means to pull in all dependencies.

It feels "obvious" for me, but I agree it's not good as a coding standard.