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.
// AnyClass.hh #ifndef OUTLINE #include "AnyClass.icc" #endif //AnyClass.cc #ifdef OUTLINE #define inline #include "AnyClass.icc" #undef inline #endif
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.
//
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 /* */
.
// // 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. //
// 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; }
*
) or references (&
) shall not
be included as include files.
#include
directives.
#include "
filename.hh"
for user-prepared include files.
#include <
filename.hh>
for include files from libraries.
what
can be used to obtain information on the file revision.
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.
#ifndef FOO_HH #define FOO_HH // The rest of the file #endif
// NOT RECOMMENDED #include <../include/fnutt.h> // NOT GUARANTEED TO WORK #include <sys/socket.h>
static const char* sccsid = "@(#) Exception.cc, rev. A, Copyright Ellemtel Utvecklings AB 1991";
// 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
// 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 ) { // ... }