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"
#endif
//AnyClass.cc
#ifdef OUTLINE
#define inline
#include "AnyClass.icc"
#undef inline
#endif
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:
- char __sti__file_cc___Fv();
//filename is file.cc
- char __std__file_cc___Fv();
//filename is file.cc
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.
4.3. Comments
- 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: [email protected]
//
// 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
// THE NEXT TWO LINES ARE STRATEGIC COMMENTS
// This function does some complicated things. It works like this:
// blah-blah-blah ...
int
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
#endif
Example 5: Never use explicit UNIX path names
// NOT RECOMMENDED
#include <../include/fnutt.h>
// NOT GUARANTEED TO WORK
#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
#ifndef PACKABLESTRING_HH
#define 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
{
public:
PackableString( const String& s );
class Buffer* put( class Buffer* outbuffer );
// ...
};
#endif
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"
Buffer*
PackableString::put( Buffer* outbuffer )
{
// ...
}
|