Discussion Topic #2: Create standalone and "guarded" header files
Monday, May 15, 2006
When writing C++ header files, you should always be conscious that your header files can be included by other projects and files. The reason for doing this is because improperly written header files can cause a number of problems including stray dependencies, unnecessary include burdens on your header file's users, and multiple inclusion/redefinitions to name a few. Two techniques that help to prevent this are making your header files standalone and using include guards. Both techniques are described below.
Make Your Headers Self-SufficientHeader files should be written so that they can stand on their own two feet. Header files that include other unneeded header files can cause stray dependencies and longer build times (because the compiler is required to parse each header file that is included at compile time). The first step in making your header files self-sufficient is to look at each header file it includes and then verify that it actually needs to include that file. This is easily verifiable by just commenting out the included file and then recompiling. If the compile fails, at least that included object's type is needed. If the compile works, you can delete the commented out include altogether. Once you have verified which includes are required, the second step is determine what objects in those header files are actually needed. If you do not need the included object's definition (i.e. You return it from a function, it is declared as a parameter in one of your functions, etc) you can forgo including the type's definition in your header. You can achieve this with a forward declaration. A forward declaration allows you to declare the use of a type without the details of its definition. Note: The only time you need the type's definition in your header is when the compiler needs to know the size of the type (i.e. your have the included object as a member of your class) or when you need to use/call a member of that type (i.e. you invoke a function on the included object). Here is a sample header that shows displays forward declares and also minimal include directives:
#include <atlbase.h> // include header for VARIANT class member class SnmpRequestResults; // forward declaration of results class class SnmpRequest { public: SnmpRequest(); ~SnmpRequest(); SnmpRequestResults* GetResults(); private: VARIANT m_vDataType; };
As you can see in the above SnmpRequest class, we use both the forward declaration and a small number of include directives (we only include atlbase.h). We have removed the need for including the SnmpRequestResults header in our header and therefore made our own header file more self-sufficient. When using these techniques, think of your header files as being used completely by itself.
Use #include GuardsMake sure that your header files are only defined once in case it is included multiple times. You can guarantee this with the use of include guards. Modern compilers recognize these guards at the top of header files and therefore prevent multiple inclusions of a header file during the compilation of a single cpp file. They are implemented with preprocessor directives. The guards need to conform to the following rules:
- The name needs to be unique.
- No code or comments should come before or after the guarded portion.
- The guards should wrap the entire object declaration.
Here is an example of include guard use:
#ifndef FOO_H_INCLUDED #define FOO_H_INCLUDED class Foo { public: Foo(); ~Foo(); int ComputeFooSize(); }; #endif
By using include guards and making header files self-sufficient, you can make your header files lightweight and therefore easier to use by other code. It also has the indirect affect of you having to write less code. That's always good. I hope this was helpful and if there are any questions, post away.
- Gilemonster
Labels: C++
posted by Gilemonster @ 8:48 PM,
2 Comments:
- At 5:09 AM, said...
-
Hi, Gilemonster! Why you mentioned "No code or comments should come before or after the guarded portion"? I found this rule is violated in many code, such as the examples from Thinking in C++. Thanks!
- At 8:25 PM, Gilemonster said...
-
You are correct that a large number of examples today do not follow bullet #2 above. To be honest, I've started violating this as I put a large block comment at the beginning of every file. For header files this means a block comment before the header guards. So I sometimes don't even follow my own rules! :-) The important thing here is that bullets #1 and #2 are followed. Thanks for the comment!