4. Source Code in Files

4.1. Structure of Code

Rule 1
Include files in C++ always have the file name extension ".hh".
Rule 2
Implementation files in C++ always have the file name extension ".cc".
Rule 3
Inline definition files always have the file name extension ".icc".
Rec. 3
An include file should not contain more than one class definition.
Rec. 4
Divide up the definitions of member functions or functions into as many files as possible.
Rec. 5
Place machine-dependent code in a special file so that it may be easily located when porting code from one machine to another.

The purpose of these conventions is to provide a uniform interpretation of file names. One reason for this is that it is easier to make tools which base their behaviour on the file name extension.

There are two kinds of include files in C++: those which contain code that is accepted by both ANSI-C and C++ compilers and those which contain code that is only accepted by C++ compilers. It is appropriate to distinguish between the two in order to avoid unpleasant compilation errors (from using the wrong kind of include file).

If a ".cc" file contains a large number of function definitions, the object file produced by the compiler may be unnecessarily large. In order to obtain the smallest possible executable files, it is necessary to have a separate file for each function definition. This is because the standard UNIX linker ld links all functions in an object file even if only one of them is actually used. It is especially important to remember that virtual functions are always linked. [Compilers based on Cfront refer to these via so-called virtual tables.] On the other hand, there are problems in managing a large number of files, since sufficiently powerful tools are not currently available. Also, the time necessary to compile a program consisting of a large number of files is longer.

Some debuggers cannot debug inline functions. By placing inline functions in a separate file and by including that file in the implementation file, thus treating the inline functions as ordinary functions, it is possible to debug the functions while testing the program. For this to work some special preprocessor techniques must be used. [See Example 1!] The inline definition file must not be included by the include file for the class and the keyword `inline' must be removed.

When tools for managing C++ code are not available, it is much easier for those who use and maintain classes if there is only one class definition in each file and if implementations of member functions in different classes are not present in the same file.

Exception to Rule 1
Include files which contain code that is accepted by both C and C++ compilers should have the file name extension ".h".
Exception to Rule 2
When using a compiler that does not accept the extension ".cc", the extension ".C" is used instead.
Exception to Rule 3
No exceptions.

Example 1: Inline definitions in a separate file for conditional compilation

   // AnyClass.hh
   #ifndef OUTLINE
   #include "AnyClass.icc"

   #ifdef OUTLINE
   #define inline
   #include "AnyClass.icc"
   #undef inline

4.2. Naming Files

Rec. 6
Always give a file a name that is unique in as large a context as possible.
Rec. 7
An include file for a class should have a file name of the form <class name> + extension. Use uppercase and lowercase letters in the same way as in the source code.

There is always a risk for name collisions when the file name is part of identifier names that are generated by the compiler. This is a problem in using any Cfront-based compiler.

AT&T's Cfront-based compiler creates two functions for every file in order to call constructors and destructors of static objects in the proper order. These functions are named:

It is easily understood that if a program has two files with the same name but in different subdirectories, there will be name collisions between the functions generated above.

Since class names must generally be unique within a large context, it is appropriate to utilize this characteristic when naming its include file. This convention makes it easy to locate a class definition using a file-based tool.


Rule 4
Every file that contains source code must be documented with an introductory comment that provides information on the file name and its contents.
Rule 5
All files must include copyright information.
Rule 6
All comments are to be written in English.
Rec. 8
Write some descriptive comments before every function.
Rec. 9
Use // for comments.

It is necessary to document source code. This should be compact and easy to find. By properly choosing names for variables, functions and classes and by properly structuring the code, there is less need for comments within the code.

Note that comments in include files are meant for the users of classes, while comments in implementation files are meant for those who maintain the classes.

All our code must be copyright marked. If the code has been developed over a period of years, each year must be stated.

The standardization of comments makes it possible to automatically generate man-pages from source code. This may be used to keep source code and documentation together until adequate tools for information management are available.

Comments are often said to be either strategic or tactical. A strategic comment describes what a function or section of code is intended to do, and is placed before this code. A tactical comment describes what a single line of code is intended to do, and is placed, if possible, at the end of this line. Unfortunately, too many tactical comments can make code unreadable. For this reason, it is recommended to primarily use strategic comments, unless trying to explain very complicated code.

If the characters // are consistently used for writing comments, then the combination /* */ may be used to make comments out of entire sections of code during the development and debugging phases. C++, however, does not allow comments to be nested using /* */.

Exception to Rule 4
No exceptions.
Exception to Rule 5
No exceptions.
Exception to Rule 6
No exceptions.

Example 2: Documentation of a file

   //  File:        test.cc
   //  Description: This is a test program
   //  Rev:         A
   //  Created:     Thur. Oct 31, 1991, 12:30:14
   //  Author:      Erik Nyquist
   //  mail:        erik.nyquist@eua.ericsson.se
   //  Copyright Ellemtel Utvecklings AB 1991
   //  BOX 1505
   //  125 25 ALVSJO
   //  SWEDEN
   //  tel int + 46 8 727 3000
   //  The copyright to the computer program(s) herein
   //  is the property of Ellemtel Utvecklings AB, Sweden.
   //  The program(s) may be used and/or copied only with
   //  the written permission of Ellemtel Utvecklings AB
   //  or in accordance with the terms and conditions
   //  stipulated in the agreement/contract under which
   //  the program(s) have been supplied.

Example 3: Strategic and Tactical Comments

   // This function does some complicated things. It works like this:
   //  blah-blah-blah ...
   insanelyGreatAndComplicatedFunction( int i )
      int index = i++ + ++i * i-- - --i;  // THIS IS A TACTICAL COMMENT

      return index;

4.4. Include Files

Rule 7
Every include file must contain a mechanism that prevents multiple inclusions of the file.
Rule 8
When the following kinds of definitions are used (in implementation files or in other include files), they must be included as separate include files:
  • classes that are used as base classes,
  • classes that are used as member variables,
  • classes that appear as return types or as argument types in function/member function prototypes.
  • function prototypes for functions/member functions used in inline member functions that are defined in the file.
Rule 9
Definitions of classes that are only accessed via pointers (*) or references (&) shall not be included as include files.
Rule 10
Never specify relative UNIX names in #include directives.
Rule 11
Every implementation file is to include the relevant files that contain:
  • declarations of types and functions used in the functions that are implemented in the file.
  • declarations of variables and member functions used in the functions that are implemented in the file.
Rec. 10
Use the directive #include "filename.hh" for user-prepared include files.
Rec. 11
Use the directive #include <filename.hh> for include files from libraries.
Rec. 12
Every implementation file should declare a local constant string that describes the file so the UNIX command what can be used to obtain information on the file revision.
Rec. 13
Never include other files in an ".icc" file.

The easiest way to avoid multiple includes of files is by using an #ifndef/#define block in the beginning of the file and an #endif at the end of the file.

The number of files included should be minimized. If a file is included in an include file, then every implementation file that includes the second include file must be re-compiled whenever the first file is modified. A simple modification in one include file can make it necessary to re-compile a large number of files.

When only referring to pointers or references to types defined in a file, it is often not necessary to include that file. It may suffice to use a forward declaration to inform the compiler that the class exists. Another alternative is to precede each declaration of a pointer to the class with the keyword class.

True portable code is independent of the underlying operating system. For this reason, relative UNIX search paths should be avoided when including files. The processing of such search paths depends on the compiler and UNIX should not be taken for granted. Instead, search paths should be provided in `make' files as options for the compiler.

If a file only contains information that is only needed in an implementation file, that file should not be included in another include file. Otherwise, when the information is no longer needed in the implementation file, it may be necessary to re-compile each file that uses the interface defined in the include file.

Every C++ course teaches the difference between the include directives for user-prepared and for library include files. If the file name is bracketed between "<" and ">", the preprocessor will not search for the file in the default directory. This reduces the risk of unintended name collisions between user-prepared and library include files.

By declaring a local constant string, the compiler becomes self-identifying. This may be used to easily determine the version of the program that is used. The string must begin with the characters @(#) to be read by the UNIX what command.

Exception to Rule 7
No exceptions.
Exception to Rule 8
No exceptions.
Exception to Rule 9
No exceptions.
Exception to Rule 10
No exceptions.
Exception to Rule 11
No exceptions.

Example 4: Technique for preventing multiple inclusion of an include file

   #ifndef FOO_HH
   #define FOO_HH

   // The rest of the file


Example 5: Never use explicit UNIX path names

   #include <../include/fnutt.h>

   #include <sys/socket.h>

Example 6: Local constant string for identifying implementation files.

   static const char* sccsid =
   "@(#) Exception.cc, rev. A, Copyright Ellemtel Utvecklings AB 1991";

Example 7: Include file for the class PackableString

   // file: PackableString.hh
   #include "String.hh"
   #include "Packable.hh"

   // It is not necessary to extern-declare class Buffer when
   // each pointer declaration specifies the keyword class as shown below.
   // An explicit extern-declaration makes the code easier to
   // understand.

   extern class Buffer;

   class PackableString : public String, public Packable
         PackableString( const String& s );
         class Buffer* put( class Buffer* outbuffer );
         // ...


Example 8: Implementation file for the class PackableString

   // PackableString.cc

   #include "PackableString.hh"

   // To be able to use Buffer-instances, Buffer.hh MUST be included.

   #include "Buffer.hh"

   PackableString::put( Buffer* outbuffer )
      // ...