.h/.hpp including strategy

Sorry to say but people are very thoughtless concerning filling up of .h-files. Often you can see just a huge dumping site of everything imaginable there. This article is devoted to classify the approach of working with .h-files. It is a result of my conversations with Anton, who I truly believe, is the best one among C++ers which I have ever seen.

Globally, there are two ways of including into .c/.cpp-files.

  1. umbrella header
  2. fine grained headers

A couple of examples: 1) ntddk.h, windows.h. Let’s assume we have foo.h, which in its turn has ntddk.h and other odds and ends, and placing this foo.h into bar1.c, bar2.c, bar3.c.

In case 2) targeted .h-files have to be included directly into .c/.cpp; and in .h-files there is absolutely minimal usage of other #include; thus we have almost empty foo.h, and placing ntddk.h into bar1.c, bar2.c, bar3.c.

A question might arise: What is more preferable 1) or 2)? Practically always the straightforward answer is 2). In case 1) intellectual efforts are minimal – through everything down in a big mess and use this mess wherever you can. It works ... till the first "error C2011: 'xxx' type redefinition", "error LNK2005: "xxx" already defined in xxx.obj" or even something worse.

By the way the infamous stdafx MSVC headers is nothing more than the old bad umbrella. Do not use them.

Case 2) requires significantly much more efforts. By mistake, on the basis of windows.h/ntddk.h, many start using MS policy: "If MS does this way, why not me?". Unfortunately, you do not, at least without prior thinking. In fact, the ones who ask this question do not realise very well why MS (or any other developer of large C-libraries) CAN do it.

Let’s say you have understood it and decided to act professionally towards .h/.hpp-files usage. Sorry, there are no ready-made recipes, rather there is a set of basic rules, and only knowing them firmly you can freely create by your own.

  1. .h/.hpp is not a dumping site. Never ever make a mess there fecklessly. Only that which represents the external interface has to be in .h/.hpp-files. The same is applicable regarding to the internal typedefs, defines and etc.

    One more aspect: it’s not by mistake that Google coding standards recommend to avoid macros usage. So again, it’s a bad habit to use #define especially in headers. The Internet is literally full of arguments discussing "#define versus const". In many-many cases it is preferable to use const. We can formulate an empiric two-step rule:
    • Wherever it’s possible, a "#define" has to replaced by a "const".
    • If for any reason you can not do 1, then think about moving of "#define" from .h to .c/.cpp if possible.


    Putting #pragma into .h/.hpp files is another thing you better avoid doing. The best example of what should never be done is right here:
          #pragma warning(disable: 4018)  // signed/unsigned mismatch
    #pragma warning(disable: 4100) // unreferenced formal parameter

    May be the logic was "if there are no more warnings and the compiler does not yell, there are no more problems and everything is nice and dandy". Don't do it this way. Ever.

    The idea itself is good: everything has to compile to /w4 without much ado from the compiler side; but the implementation can be different. Let’s say if it has been decided to use something like "while(1) + break". Of course MSVC will scream "conditional expression is constant". Well, the compiler does its job and we have to do ours. The warning must be eliminated. The question is how. A stupid way to do so is just do it directly in .h-file; the smarter one is to do it globally in .cpp-file. Finally, the wisest approach is like that:
          #pragma warning(disable:4127)
    while(0){
    #pragma warning(default:4127)


    C++ is /exceptionally/ sensitive to the dumping into .h. In the best case you will have to pay for that mess by extremely slow compilation. But if worst comes to worst... Brrrr... Better be without nightmares for the up-coming night. That’s why the rules for C++ become more severe.
    1. There has to be minimal amount of includes in the C++ .hpp files with templates.
    2. Forward references have to be actively used (it destroys so called cyclic dependency).


    Do not use this:
          #include "Bar.h"
    class Foo {
    Bar* B;
    };


    use this if possible:
          class Bar;
    class Foo {
    Bar* B;
    };

    There are a couple of more nuances regarding to forward declarations:


    Pay attention: a forward declaration of "class foo" will not fly in cases when you have to know sizeof(foo). Neither it will fly with STL. This will fail:
          class string;


    This will also fail:
          class std::string;


    Generally speaking it is quite easy to make namespaces and forward references to become friends:
          namespace bazaar
    {
    class catch_me_if_you_can;
    }


    but something like
          namespace std
    {
    class string;
    }


    will also fail.

    Reason being std::string is no just a class, it is a template. Therefore "non-template" version of forward reference will not work with it. If you wish to make it work no matter what try something like:
          namespace std {
    template<typename _Alloc> class allocator;
    template<class _CharT> struct char_traits;
    template<typename _CharT, typename _Traits,
    typename _Alloc > class basic_string;
    typedef basic_string<char,
    char_traits<char>, allocator<char> > string;
    }


    And now tell me. Do you still seriously think of including something like *this* into your headers? I hope the answer is "no" so we will stick to using good old #include <string>. Until you do not overuse std::map in your header files everything should be more or less ok. Try placing all STL includes in your .cpp files though.

    By the way, iosfwd is an excellent example if one wishes to make themselves familiar with forward declarations on template classes.
  2. Main include rule. If there is an .h-file for .c, then always it has to be included into .c. At least it will help to avoid incompatibility between "declaration" and "definition" if, let’s say, we have something like:
          void bar(int a, int b);

    in .h file and:
          void bar(int a) {
    ...
    }


    in .c file. The "main include" rule will help to catch the similar cases and avoid hard-to-catch problems.
  3. Do not allow re-inclusion (duplication) of .h/.hpp-files. Clever people say "The header file inclusion mechanism should be tolerant to
    duplicate header file inclusions". We can do it two ways:

    Which one is preferable? Well, using pragma once is not recommended. And it looks like the MS guys honestly cannot understand why! Reading the answer int19h is a must – that’s the whole point.
    It is recommended using the connection #ifndef + #define + #endif, or another variant – hybrid:
          #ifndef FILENAME_H_
    #define FILENAME_H_
    #if defined(_MSC_VER) && (_MSC_VER >= 1200)
    #pragma once
    #endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
    ...
    #endif // FILENAME_H_

    If you use #ifdef then think about mangling strategy. It means that #ifndef FILENAME_H_ is not enough probably, because "name clash” may happen. Assume you have a couple of conf.h-files for the same project – here a collision is. It might worth to take a look at the package titling policy in java.

The choice is yours. The header file should be so designed that the order of header file inclusion is not important. Sorry to say, the headers like Windows, as well as libc, do not match these requirements.

Do your best that your .h-files will not cause the hidden surprises. MSVC /FI option also has similar roots.

cpp
Posted by: volodya  7.3.2009 at 05:35   ‌ ‌   0 comments
Only authorized users can post comments.

You're currently an anonymous user. Just browsing around? That's totally cool with us. We won't bug you until you're ready to write a comment. Otherwize you have to enter your OpenID credentials to log in. If you have not one, you can easily create it!

Example OpenIDs:

  • http://openid.aol.com/yourname
  • http://yourname.myopenid.com/
  • https://me.yahoo.com/yourname (alternately, http://yahoo.com/ works too)
  • http://claimid.com/yourname
  • http://yourname.wordpress.com/
  • http://yourname.blogspot.com/
  • http://technorati.com/people/technorati/yourname
  • http://yourname.pip.verisignlabs.com/
  • http://yourname.livejournal.com/
  • http://www.flickr.com/photos/yourname
Please note that you must enable OpenID support with your preferred provider!

WHAT'S NEW RSS Whats New

PEP8 validation script
Modified PEP8 validation script with Nesting Depth additional validation.
nesting depth, python
October 21, 2010
PEP8 and nesting depth metric
Company code style is one of the most essential policies to follow for any programming-related IT-organization. It helps to organize interaction between developers, especially for Agile teams, makes code more ...
code coverage, metrics, nesting depth, python
October 21, 2010
CodeExample plugin for Trac
The Trac plugin for code examples colouring. It supports three types of examples - a simple, a correct one and an incorrect. Further details see at
pygments, Trac
September 29, 2010
A couple of words about TDD
Unit-test coding supposes to be one of the most significant methodological achievements of the industry, let’s say, for about last 15 years. The Internet is full of enthusiastic exclamations [1, ...
code coverage, code review, metrics, TDD, test first, test last
February 21, 2010
Metrics - LoC
This is going to be a small set of articles devoted to metrics. The first one is about LoC - Line of Code. I think that the first reaction on ...
LoC, metrics
May 11, 2009