Chapter Two

Organizing the  code

Code is most often stored in files, even though some development environments also have other, more efficient representations as an alternative (for example precompiled headers). Guidelines for how the code is organized in files are needed to make the code easy to compile.

RULES
AND
RECOMMENDATIONS

Rule 2.1 Each header file should be self-contained.

Rule 2.2 Avoid unnecessary inclusion.

Rule 2.3 Enclose all code in header files within include guards.

Rec 2.4 Definitions for inline member functions should be placed in a separate file.

Rec 2.5 Definitions for all template functions of a class should be placed in a separate file.

Style 1.6 - Style 1.7 , how include guards are written.

Style 1.9 - Style 1.10 , how file names are chosen.

Style 1.15 , where inline functions are defined.

Rule 2.1 Each header file should be self-contained.

The purpose of a header file is to group type definitions, declarations and macros. It should be self-contained so that nothing more than the inclusion of a single header file should be needed to use the full interface of a class. A rather common error is to forget to include a necessary header file. This could happen for example when a header file has not been tested in isolation. By pure coincidence, the forgotten file is included by another file. One way to test your header file is to always include it first in the corresponding implementation files. For this to work, the header file must be self-contained.

Testing for self-containment

 
// EmcArray.cc

#include "EmcArray.hh"
#include <iostream.h>
// ...

// The rest of the EmcArray.cc file

Rule 2.2 Avoid unnecessary inclusion.

The opposite, too much inclusion, is even more common. Very often a file is included more than once, since it is required to make different header files self-contained. It is also common that a file is included even though it is not needed at all.

Before an object can be created, its size must be known and that size can only be found by inspecting the class definition. If an object of the class is used as return value, argument, data member or variable with static storage duration, the header file containing the class definition must be included.

It should be enough to forward-declare a class if it is only referred to by pointer or reference in the header file. There are some important exceptions however. The class definition must be included if a member function is called or a pointer is dereferenced. It should also be included if a pointer or reference is cast to another type.

Remember that the inclusion of a header file makes the implementation of inline member functions visible to the user. If the implementation of an inline member function operates upon an object of a class, that class definition must be visible even though only pointers or references are used. The presence of inline member functions increases the number of files that must be recompiled when a class definition is modified. You can shorten your compile time by avoiding inline functions, but that may instead reduce the run time performance of your program.

If an inline function contains casts between forward-declared types, no inclusion is needed, but such an implementation has a potential bug. If two classes are forward-declared and they are related through inheritance, a cast will not give the correct result if multiple inheritance is used and pointer-adjustments are required. This is another case that requires the class definitions to be visible.

Data member of class type

 
#include "A.hh"

class X
{
   public:
      A    returnA();            // A.hh must be #included
      void withAParameter(A a);  // A.hh must be #included
   private:
      A  aM;                     // A.hh must be #included
};

Forward declaration

 
// Forward declaration

class B;

class Y
{
   public:
      B*   returnBPtr();
      void withConstBRefParameter(const B& b);
   private:
      B*   bM;
};

Rule 2.3 Enclose all code in header files within include guards.

Header files are often included many times in a program. A standard header such as string.h is a good example. Since C++ does not allow multiple definitions of a class, it is necessary to prevent the compiler from reading the definitions more than once. The standard, as well as the only portable technique is to use an include guard so that the source code is only seen the first time the compiler reads the file. By defining a macro inside a conditional preprocessor directive, which is only true if the macro has not been defined, the preprocessor prevents the compiler from seeing the source code in a header file more than once.

It is important to have unique macros among the set of header files, or only one of the header files using the same macro name will be seen by the compiler. If there are no files in your system with identical names, and you have a one-to-one correspondence between the macro name and the file name, this should not be a problem.

Life as a programmer is much easier if there is a sensible mapping between the name of a file and its content. For example, nobody is going to like you if you do a senseless thing like putting the String class in the file Stack.hh . The ideal is to have one file for each class, since that makes it very easy to give a good name to the file, but quite often this is not possible. One such reason is if you are constrained to use very short file names by an operating system like MS-DOS.

In such cases it is reasonable to put several class definitions in the same header file, but only if the classes are closely related. It is much easier to give a good name to such a collection of classes than if they are grouped arbitrarily. But what is more important is the fact that there is less risk that classes are included without reason.

A classic example is a list class, which often provides a special iterator class for iteration over the list. Because the iterator is useless without the list it is natural to put both the list class and the iterator class in the same file. An advantage of doing so is that the user will only need to include one file to use the list abstraction. With separate header files for each class you need to find unique names for even more files, which can be difficult if you are constrained by an operating system like MS-DOS.

Include guard

 
#ifndef EMCQUEUE_HH
#define EMCQUEUE_HH

// Rest of header file

#endif

Rec 2.4 Definitions for inline member functions should be placed in a separate file.

In the Style appendix we recommend that all i nline member functions should be defined outside of the class definition. By having definitions of inline functions outside the class, the class declaration will be much easier to read. The best place to put such inline functions is in a separate file, an inline definition file.

An inline definition file should normally be included by the corresponding header file. Sometimes frequent changes to inline definition files make the compilation times unnecessary long, and if that is a problem, inline definition files are best included by the implementation file. It is necessary to remove all inline keywords first, otherwise you will get link errors. With macros, such changes can be made without changing the source code.

Disable inlining by using inline definition files

EmcString.icc

 
#include <string.h>
// ...

// Do not include anything after this point

#ifdef DISABLE_INLINE
#define inline
#endif

// Definitions of inline functions

inline
const char* EmcString::cStr() const
{
   return cpM;
}

// ...

#ifdef DISABLE_INLINE
#undef inline
#endif

EmcString.hh

 
// Class declaration

// ...

// Always include at end

#ifndef DISABLE_INLINE
#include "EmcString.icc"
#endif

EmcString.cc

 
#include "EmcString.hh"

// Definitions of non-inline functions

// ...

// Always include at end

#ifdef DISABLE_INLINE
#include "EmcString.icc"
#endif

Rec 2.5 Definitions for all template functions of a class should be placed in a separate file.

Templates are in one respect very similar to an inline function. No code is generated when the compiler sees the declaration of a template; code is generated only when a template instantiation is needed.

A function template instantiation is needed when the template is called or its address is taken and a class template instantiation is needed when an object of the template instantiation class is declared.

A big problem is that there is no standard for how code that uses templates is compiled. The compilers that require the complete template definition to be visible usually instantiate the template whenever it is needed and then use a flexible linker to remove redundant instantiations of the template member function. However, this solution is not possible on all systems; the linkers on most UNIX-systems cannot do this.

A big problem is that even though there is a standard for how templates should be compiled, there are still many compilers that do not follow the standard. Some compilers require the complete template definition to be visible, even though that is not required by the standard.

This means that we have a potential portability problem when writing code that uses templates. We recommend that you to put the implementation of template functions in a separate file, a template definition file, and use conditional compilation to control whether that file is included by the header file. A macro is either set or not depending on what compiler you use. An inconvenience is that you now have to manage more files.

There could also a file with template functions that are declared inline . These should not be put in a template definition file.

Function template

 
template <class T>
T max(T x, T y)
{
   return (x > y) ? x : y;
}

void function(int i, int j)
{
   int m = max(i,j);  // must instantiate max(int,int)
   // ...
}

Class template

 
template <class T>
class EmcQueue
{
   public:
      EmcQueue();
      // ...
      void insert(const T& t);
};

EmcQueue<int> q;    // instantiate EmcQueue<int>
q.insert(42);       // instantiate EmcQueue<int>::insert

Template header file

EmcQueue.hh

 
template <class T>
class EmcQueue
{
   // ...
};
 
#ifndef DISABLE_INLINE
#include "EmcQueue.icc"
#endif

#ifndef EXTERNAL_TEMPLATE_DEFINITION
#include "EmcQueue.cc"
#endif