C++ Programming Guidelines
Copyright © 1997 Rational Software Corporation.
All rights reserved.
The word "Rational" and Rational's products are trademarks of Rational
Software Corporation. References to other companies and their products use
trademarks owned by the respective companies and are for reference purpose only.
This document was prepared for Rational Software Corp. by Luan Doan-Minh, from
Calypso Software Inc., Vancouver, B.C., Canada.
Contents
Chapter 1
Introduction
Large software projects are generally undertaken by correspondingly large
teams of developers. For the code produced by large teams to have project-wide
measurable quality, the code must be written in accordance with; and be judged
against a standard. It is therefore important for large project teams to
establish a programming standard, or set of guidelines.
The use of a programming standard also makes it possible:
- to foster the development of robust, readable, easier to maintain code;
and to reduce the mental programming efforts required from both experienced
and not so experienced developers alike;
- to enforce a consistent project-wide coding style;
- to apply quality measures to the resultant software, both by human and
automated means;
- to quickly acclimatize new developers to the project culture;
- to support the reuse of project resources: to allow developers to be moved
from one project area (or sub-project team) to another without requiring
re-learning of new sub-project team cultures.
The aim of this text is to present C++ programming rules, guidelines and
hints (generically referred to as guidelines) that can be used as the basis for
a standard. It is intended for software engineers working in large project
teams.
The current version is purposely focused upon programming (although at times it
is difficult to draw the line between programming and design); design guidelines
will be added at a later date.
The guidelines presented cover the following aspects of C++ development:
- how the project code should be organized;
- the programming style (how source code should actually be written);
- how the code should be documented at the source level;
- the naming conventions to be employed for both source files and names
within the code;
- when certain language constructs should be used, and when they should be
avoided;
They have been collected from a large base of industry knowledge. (See the
bibliography for the sources: authors and references.) They are based upon:
- well-known software principles;
- "good" software practices;
- lessons learned;
- subjective opinions.
Most are based upon a handful of the first category, and large doses of the
second and third. Unfortunately, some are also based upon the last category;
mainly, because programming is a highly subjective activity: there being no
widely accepted "best" or "right" way to code everything.
Fundamental Principles
Clear, understandable C++ source code is the primary goal of most of the
rules and guidelines: clear, understandable source code being a major
contributing factor to software reliability and maintainability. What is meant
by clear and understandable code can be captured in the following three simple
fundamental principles [Kruchten, 94].
Minimal Surprise-Over its lifetime, source code is read more often than it is
written, especially specifications. Ideally, code should read like an
English-language description of what is being done, with the added benefit hat
it executes. Programs are written more for people than for computers. Reading
code is a complex mental process that can be eased by uniformity, also referred
to in this guide as the minimal-surprise principle. A uniform style across an
entire project is a major reason for a team of software developers to agree on
programming standards, and it should not be perceived as some kind of punishment
or as an obstacle to creativity and productivity.
Single Point of Maintenance-Whenever possible, a design decision should be
expressed at only one point in the source, and most of its consequences should
be derived programmatically from this point. Violations of this principle
greatly jeopardize maintainability and reliability, as well as
understandability.
Minimal Noise-Finally, as a major contribution to legibility, the minimal-noise
principle is applied. That is, an effort is made to avoid cluttering the source
code with visual "noise": bars, boxes, and other text with low
information content or information that does not contribute to the understanding
of the purpose of the software.
The intended spirit of the guidelines expressed herein, is to not be overly
restrictive; but, rather to attempt to provide guidance for the correct and safe
usage of language features. The key to good software resides in:
knowing each feature, its limitations and potential dangers;
knowing exactly in which circumstances the feature is safe to use;
making the decision to use the feature highly visible;
using the feature with care and moderation, where appropriate.
Assumptions
The guidelines presented here make a small number of basic assumptions:
The reader knows C++
The use of advanced C++ features is encouraged wherever beneficial, rather than
discouraged on the ground that some programmers are unfamiliar with them. This
is the only way in which the project can really benefit from using C++. C++
should not be used as if it were C, in fact the object-oriented features of C++
preclude its use as in C. Paraphrasing the code in comments is discouraged; on
the contrary, the source code should be used in place of comments wherever
feasible.
Follow large project practice.
Many rules offer the most value in large systems, although they can also be used
in a small system, if only for the sake of practice and uniformity at the
project or corporate level.
Coding follows an object-oriented design
Many rules will support a systematic mapping of object-oriented (OO) concepts to
C++ features and specific naming conventions.
Classification of Guidelines
Guidelines are not of equal importance; they are weighted using the following
scale:
Tip:
A guideline identified by the above symbol is a tip a simple piece of advice
that can be followed, or safely ignored.
Recommendation:
A guideline identified by the above symbol is a recommendation usually based on
more technical grounds: encapsulation, cohesion, coupling, portability or
reusability may be affected, as well as performance in some implementations.
Recommendations must be followed unless there is good justification not to.
Requirement or Restriction:
A guideline identified by the above symbol is a requirement or restriction; a
violation would definitely lead to bad, unreliable, or non-portable code.
Requirements or restrictions cannot be violated without a waiver
The First and Last Guideline
Use
common sense
When you cannot find an applicable rule or guideline; when a rule obviously
does not apply; or when everything else fails: use common sense, and check the
fundamental principles. This rule overrides all of the others. Common sense is
required even when rules and guidelines exist.
Chapter 2
Code Organization and Style
This chapter provides guidance on program structure and lay-out.
Code Structure
Large systems are usually developed as a number of smaller functional subsystems.
Subsystems themselves are usually constructed from a number of code modules.
In C++, a module normally contains the implementation for a single, or on rare
occasions, a set of closely related abstractions. In C++, an abstraction
is normally implemented as a class. A class has two distinct components:
an interface visible to the class clients, providing a declaration or
specification of the class capabilities and responsibilities; and an implementation
of the declared specification (the class definition).
Similar to the class, a module also has an interface and an implementation: the
module interface contains the specifications for the contained module
abstractions (class declarations); and the module implementation contains the
actual implementation of the abstractions (class definitions).
In the construction of the system; subsystems may also be organized into
collaborative groups or layers to minimize and control their dependencies.
Place
module specifications and implementations in separate files
A module's specification should be placed in a separate file from its
implementation-the specification file is referred to as a header. A
module's implementation may be placed in one or more implementation files.
If a module implementation contains extensive inline functions, common
implementation-private declarations, test code, or platform-specific code, then
separate these parts into their own files and name each file after its part's
content.
If program executable sizes are a concern, then rarely used functions should
also be placed in their own individual files.
Construct a part file name in the following manner:
- Use the module's main abstraction name as the module name.
- Append a part type name to the module name. Choose part type names that
are indicative of their content.
- The module name and part name should be separated by a separator (e.g. '_'
(underscore) or '.' (period)); choose a separator and apply it consistently.
- For better predictability, use the same letter case for file names as for
names within the code. /UL>
The following is an example module partitioning and naming scheme:
- module.inlines.cc-if a module has many potentially inline-able
functions, then place the function definitions in a separate .
inlines
file (see "Place module inline function definitions in a separate
file").
- module.private.hh-if a module has many common
implementation-private declarations that are referenced by other parts,
then separate these declarations out into a .private part for
inclusion by other implementation files.
- module.private.cc-a module's implementation-private function
definitions, separated out for editing convenience.
- module.function_name.cc-if executable size is a concern, then
specialized member functions that are not required by many programs
should be separated out into their own individual implementation files
(see "Break large modules into multiple translation units if
program size is a concern"). If overloaded functions are placed in
separate files, each file function name should be suffixed with an
instance number. E.g., function_name1 for the first instance of a
separate overloaded function.
- module.nested_class.cc-the member functions of a module's
nested class, placed in their own file.
- module.test.[hh\cc]-if a module requires extensive test code,
then the test code should be declared in a friend test class. The friend
test class should be called
Module.Test . Declaring
the test code as a friend class facilitates the independent development
of the module and its test code; and allows the test code to be omitted
from the final module object code without source changes.
- module.platform_name.cc-separate out any module platform
dependencies and call the part name after the platform name (see
"Isolate platform dependencies").
Choose a module partitioning and naming scheme and apply it consistently.
Example
SymaNetwork.hh // Contains the declaration for a
// class named "SymaNetwork".
SymaNetwork.Inlines.cc // Inline definitions sub-unit
SymaNetwork.cc // Module's main implementation unit
SymaNetwork.Private.cc // Private implementation sub-unit
SymaNetwork.Test.cc // Test code sub-unit
Rationale
Separating out a module's specification from its implementation
facilitates independent development of user and supplier code.
Breaking a module's implementation into multiple translation units provides
better support for object code removal, resulting in smaller executable
sizes.
Using a regular and predictable file naming and partitioning convention
allows a module's content and organization to be understood without
inspection of its actual contents.
Passing names through from the code to the file name increases
predictability and facilitates the building of file-based tools without
requiring complex name mapping [Ellemtel, 1993].
Pick
a single set of file name extensions to distinguish headers from
implementation files
Commonly used file name extensions are: .h, .H, .hh, .hpp, and
.hxx for header files; and .c, .C, .cc, .cpp, and
.cxx for implementations. Pick a set of extensions and use them
consistently.
Example
SymaNetwork.hh // The extension ".hh" used to designate
// a "SymaNetwork" module header.
SymaNetwork.cc // The extension ".cc" used to designate
// a "SymaNetwork" module implementation.
Notes
The C++ draft standard working paper also uses the extension
".ns" for headers encapsulated by a namespace.
Avoid
defining more than one class per module specification
Only upon rare occasions should multiple classes be placed together in a
module; and then only if they are closely associated (e.g., a container and
its iterator). It is acceptable to place a module's main class and its
supporting classes within the same header file if all classes are always
required to be visible to a client module.
Rationale
Reduces a module's interface and others' dependencies upon it.
Avoid
putting implementation-private declarations in module specifications
Aside from class private members, a module's implementation-private
declarations (e.g. implementation types and supporting classes) should not
appear in the module's specification. These declarations should be placed in
the needed implementation files unless the declarations are needed by
multiple implementation files; in that case the declarations should be
placed in a secondary, private header file. The secondary, private header
file should then be included by other implementation files as needed.
This practice ensures that:
a module specification cleanly expresses its abstraction and is free of
implementation artifacts;
a module specifications is kept as small as possible and thus minimizes
inter-module compilation dependencies (see also "Minimize compilation
dependencies");
Example
// Specification of module foo, contained in file "foo.hh"
//
class foo
{
.. declarations
};
// End of "foo.hh"
// Private declarations for module foo, contained in file
// "foo.private.hh" and used by all foo implementation files.
... private declarations
// End of "foo.private.hh"
// Module foo implementation, contained in multiple files
// "foo.x.cc" and "foo.y.cc"
// File "foo.x.cc"
//
#include "foo.hh" // Include module's own header
#include "foo.private.hh" // Include implementation
// required declarations.
... definitions
// End of "foo.x.cc"
// File "foo.y.cc"
//
#include "foo.hh"
#include "foo.private.hh"
... definitions
// End of "foo.y.cc"
Always
use #include to gain access to a module's specification
A module that uses another module must use the preprocessor #include
directive to acquire visibility of the supplier module's specification.
Correspondingly, modules should never re-declare any part of a supplier
module's specification.
When including files, only use the #include <header>
syntax for "standard" headers; use the #include
"header" syntax for the rest.
Use of the #include directive also applies to a module's own
implementation files: a module implementation must include its own
specification and private secondary headers (see "Place module
specifications and implementations in separate files").
Example
// The specification of module foo in its header file
// "foo.hh"
//
class foo
{
... declarations
};
// End of "foo.hh"
// The implementation of module foo in file "foo.cc"
//
#include "foo.hh" // The implementation includes its own
// specification
... definitions for members of foo
// End of "foo.cc"
An exception to the #include rule is when a module only uses
or contains a supplier module's types (classes) by-reference (using pointer
or reference-type declarations); in this case the by-reference usage or
containment is specified using a forward declaration (see also
"Minimize compilation dependencies") rather than a #include
directive.
Avoid including more than is absolutely needed: this means that module
headers should not include other headers that are required only by the
module implementation.
Example
#include "a_supplier.hh"
class needed_only_by_reference;// Use a forward declaration
//for a class if we only need
// a pointer or a reference
// access to it.
void operation_requiring_object(a_supplier required_supplier, ...);
//
// Operation requiring an actual supplier object; thus the
// supplier specification has to be #include'd.
void some_operation(needed_only_by_reference& a_reference, ...);
//
// Some operation needing only a reference to an object; thus
// should use a forward declaration for the supplier.
Rationale
This rule ensures that:
- there is always only a single declaration of a module's interface, and
that all clients see exactly the same interface;
- the compilation dependencies between modules is minimized;
- clients don't needlessly incur compilation overhead for code that is
not required.
Place
module inline function definitions in a separate file
When a module has many inline functions, their definitions should be
placed in a separate, inline-function-only file. The inline function file
should be included at the end of the module's header file.
See also "Use a No_Inline conditional compilation symbol
to subvert inline compilation".
Rationale
This technique keeps implementation details from cluttering a module's
header; thus, preserving a clean specification. It also helps in reducing
code replication when not compiling inline: using conditional compilation,
the inline functions can be compiled into a single object file as opposed to
being compiled statically into every using module. Correspondingly, inline
function definitions should not be defined in class definitions unless they
are absolutely trivial.
Break
large modules into multiple translation units if program size is a concern
Break large modules into multiple translation units to facilitate
un-referenced code removal during program linking. Member functions that are
rarely referenced should be segregated into separate files from those that
are commonly used. In the extreme, individual member functions can be placed
in their own files [Ellemtel, 1993].
Rationale
Linkers are not all equally capable of eliminating un-referenced code
within an object file. Breaking large modules into multiple files allows
these linkers to reduce executable sizes by eliminating the linking of whole
object files [Ellemtel, 1993].
Notes
It may also be worthwhile considering first whether the module should be
broken down into smaller abstractions.
Isolate
platform dependencies
Separate out platform-dependent code from platform-independent code; this
will facilitate porting. Platform-dependent modules should have file names
qualified by their platform name to highlight the platform dependence.
Example
SymaLowLevelStuff.hh // "LowLevelStuff"
// specification
SymaLowLevelStuff.SunOS54.cc // SunOS 5.4 implementation
SymaLowLevelStuff.HPUX.cc // HP-UX implementation
SymaLowLevelStuff.AIX.cc // AIX implementation
Notes
From an architectural and maintenance viewpoint, it is also good practice
to contain platform dependencies in a small number of low level subsystems.
Adopt a standard file content structure and apply it consistently
A suggested file content structure consists of the following parts in the
following order:
1. Repeated inclusion protection (specification only).
2. Optional file and version control identification.
3. File inclusions needed by this unit.
4. The module documentation (specification only).
5. Declarations (class, type, constants, objects and functions) and
additional textual specifications (preconditions and postconditions, and
invariants).
6. Inclusion of this module's inline function definitions.
7. Definitions (objects and functions) and implementation private
declarations.
8. Copyright notice.
9. Optional version control history.
Rationale
The above file content ordering, presents the client pertinent
information first; and is consistent with the rationale for the ordering of
a class' public, protected and private sections.
Notes
Depending upon corporate policy, the copyright information may need to be
placed at the top of the file.
Protect
against repeated file inclusions
Repeated file inclusion and compilation should be prevented by using the
following construct in each header file:
#if !defined(module_name) // Use preprocessor symbols to
#define module_name // protect against repeated
// inclusions... // Declarations go here
#include "module_name.inlines.cc" // Optional inline
// inclusion goes here.
// No more declarations after inclusion of module's
// inline functions.
#endif // End of module_name.hh
Use the module file name for the inclusion protection symbol. Use the
same letter-case for the symbol as for the module name.
Use
a "No_Inline " conditional compilation symbol to
subvert inline compilation
Use the following conditional compilation construct to control inline
versus out-of-line compilation of inline-able functions:
// At the top of module_name.inlines.hh
#if !defined(module_name_inlines)
#define module_name_inlines
#if defined(No_Inline)
#define inline // Nullify inline keyword
#endif
... // Inline definitions go here
#endif // End of module_name.inlines.hh
// At the end of module_name.hh
//
#if !defined(No_Inline)
#include "module_name.inlines.hh"
#endif
// At the top of module_name.cc after inclusion of
// module_name.hh
//
#if defined(No_Inline)
#include "module_name.inlines.hh"
#endif
The conditional compilation construct is similar to the multiple
inclusion protection construct. If the No_Inline symbol is not
defined, then the inline functions are compiled with the module
specification and automatically excluded from the module implementation. If
the No_Inline symbol is defined, then the inline definitions
are excluded from the module specification but included in the module
implementation with the keyword inline nullified.
Rationale
The above technique allows for reduced code replication when inline
functions are compiled out-of-line. By using conditional compilation, a
single copy of the inline functions is compiled into the defining module;
versus replicated code, compiled as "static" (internal linkage)
functions in every using module when out-of-line compilation is specified by
a compiler switch.
Notes
Use of conditional compilation increases the complexity involved in
maintaining build dependencies. This complexity is managed by always
treating headers and inline function definitions as a single logical unit:
implementation files are thus dependent upon both header and inline function
definition files.
Code Style
Use
a small, consistent indentation style for nested statements
Consistent indentation should be used to visually delineate nested
statements; indentation of between 2 and 4 spaces has been proven to be the
most visually effective for this purpose. We recommend using a regular
indentation of 2 spaces.
The compound or block statement delimiters ({} ), should be at
the same level of indentation as surrounding statements (by implication,
this means that {} are vertically aligned). Statements within
the block should be indented by the chosen number of spaces.
Case labels of a switch statement should be at the same
indentation level as the switch statement; statements within
the switch statement can then be indented by 1 indentation
level from the switch statement itself and the case labels.
Example
if (true)
{ // New block
foo(); // Statement(s) within block
// indented by 2 spaces.
}
else
{
bar();
}
while (expression)
{
statement();
}
switch (i)
{
case 1:
do_something();// Statements indented by
// 1 indentation level from
break; // the switch statement itself.
case 2:
//...
default:
//...
}
Rationale
An indentation of 2 spaces is a comprise between allowing easy
recognition of blocks, and allowing sufficient nested blocks before code
drifts too far off the right edge of a display monitor or printed page.
Indent
function parameters from the function name or scope name
If a function declaration cannot fit on a single line, then place the
first parameter on the same line as the function name; and subsequent
parameters each on a new line, indented at the same level as the first
parameter. This style of declaration and indentation, shown below, leaves
white spaces below the function return type and name; thus, improving their
visibility.
Example
void foo::function_decl( some_type first_parameter,
some_other_type second_parameter,
status_type and_subsequent);
If following the above guideline would cause line wrap, or parameters to
be too far indented, then indent all parameters from the function name or
scope name (class, namespace), with each on a separate line:
Example
void foo::function_with_a_long_name( // function name is
// much less visible
some_type first_parameter,
some_other_type second_parameter,
status_type and_subsequent);
See also alignment rules below.
Use
a maximum line length that would fit on the standard printout paper size
The maximum length of program lines should be limited to prevent loss of
information when printed on either standard (letter) or default printout
paper size.
Notes
If the level of indentation causes deeply nested statements to drift too
far to the right, and statements to extend much beyond the right margin,
then it is probably a good time to consider breaking the code into smaller,
ore manageable, functions.
Use
consistent line folding
When parameter lists in function declarations, definitions and calls, or
enumerators in an enum declarations cannot fit on a single line, break the
line after each list element and place each element on a separate line (see
also "Indent function parameters from the function name or scope
name").
Example
enum color { red,
orange,
yellow,
green,
//...
violet
};
If a class or function template declaration is overly long, fold it onto
consecutive lines after the template argument list. For example (declaration
from the standard Iterators Library, [X3J16, 95]):
template <class InputIterator, class Distance>
void advance(InputIterator& i, Distance n);
HR ALIGN="LEFT">
Chapter 3
Comments
This chapter provides guidance on the use of comments in the code.
Comments should be used to complement source code, never to
paraphrase it:
UL>
- They should supplement source code by explaining what is not obvious; they
should not duplicate the language syntax or semantics.
- They should help the reader to grasp the background concepts, the
dependencies, and especially complex data encoding or algorithms.
- They should highlight: deviations from coding or design standards; the use
of restricted features; and special "tricks."
For each comment, the programmer should be able to easily answer the
question: "What value is added by this comment?" Generally,
well-chosen names often eliminate the need for comments. Comments, unless they
participate in some formal Program Design Language (PDL), are not checked by the
compiler; therefore, in accordance with the single-point-of-maintenance
principle, design decisions should be expressed in the source code rather than
in comments, even at the expense of a few more declarations.
Use
C++ style comments rather than C-style comments
The C++ style "// " comment delimiter should be used in
preference to the C-style "/*...*/ ".
Rationale
P>C++ style comments are more visible and reduce the risk of accidentally
commenting-out vast expanses of code due to a missing end-of-comment delimiter.
Counter-Example
/* start of comment with missing end-of-comment delimiter
do_something();
do_something_else(); /* Comment about do_something_else */
// End of comment is here. ^
// Both do_something and
// do_something_else
// are accidentally commented out!
Do_further();
Maximize
comment proximity to source code
Comments should be placed near the code they are commenting upon; with the
same level of indentation, and attached to the code using a blank comment line.
Comments that apply to multiple, successive source statements should be placed above
the statements-serving as an introduction to the statements. Likewise, comments
associated with individual statements should be placed below the
statements.
Example
// A pre-statements comment applicable
// to a number of following statements
//
...
void function();
//
// A post-statement comment for
// the preceding statement.
Avoid
end of line comments
Avoid comments on the same line as a source construct: they often become
misaligned. Such comments are tolerated, however, for descriptions of elements
in long declarations, such as enumerators in an enum declaration.
Avoid
comment headers
Avoid the use of headers containing information such as author, phone
numbers, dates of creation and modification: author and phone numbers rapidly
become obsolete; whilst creation and modification dates, and reasons for
modification are best maintained by a configuration management tool (or some
other form of version history file).
Avoid the use of vertical bars, closed frames or boxes, even for major construct
(such as functions and classes); they just add visual noise and are difficult to
keep consistent. Use blank lines to separate related blocks of source ode rather
than heavy comment lines. Use a single blank line to separate constructs within
functions or classes. Use double blank lines to separate functions from each
other.
Frames or forms may have the look of uniformity, and of reminding the programmer
to document the code, but they often lead to a paraphrasing style
[Kruchten, 94].
Use
an empty comment line to separate comment paragraphs
Use empty comments, rather than empty lines, within a single comment block to
separate paragraphs
Example
// Some explanation here needs to be continued
// in a subsequent paragraph.
//
// The empty comment line above makes it
// clear that this is another
// paragraph of the same comment block.
Avoid
redundancy
Avoid repeating program identifiers in comments, and replicating information
found elsewhere-provide a pointer to the information instead. Otherwise any
program change may require maintenance in multiple places. And failure to make
the required comment changes everywhere will result in misleading or wrong
comments: these end up being worse than no comments at all.
Write
self-documenting code rather than comments
Always aim to write self-documenting code rather than providing comments.
This can be achieved by choosing better names; using extra temporary variables;
or re-structuring the code. Take care with style, syntax, and spelling in
comments. Use natural language comments rather than telegraphic, or cryptic
style.
Example
Replace:
do
{
...
} while (string_utility.locate(ch, str) != 0);
// Exit search loop when found it.
with:
do
{
...
found_it = (string_utility.locate(ch, str) == 0);
} while (!found_it);
Document
classes and functions
Although self-documenting code is preferred over comments; there is generally
a need to provide information beyond an explanation of complicated parts of the
code. The information that is needed is documentation of at least the following:
- the purpose of each class;
- the purpose of each function if its purpose is not obvious from its name;
- the meaning of any return values; e.g., the meaning of a Boolean return
value for a non-predicate function: that is, does a true value mean the
function was successful;
- conditions under which exceptions are raised;
- preconditions and postconditions on parameters, if any;
- additional data accessed, especially if it is modified: especially
important for functions with side-effects;
- any limitations or additional information needed to properly use the class
or function;
- for types and objects, any invariants or additional constraints that
cannot be expressed by the language.
Rationale
The code documentation in conjunction with the declarations should be
sufficient for a client to use the code; documentation is required since the
full semantics of classes, functions, types and objects cannot be fully
expressed using C++ alone.
Chapter 4
Naming
This chapter provides guidance on the choice of names for various C++
entities.
General
Coming up with good names for program entities (classes, functions, types,
objects, literals, exceptions, namespaces) is no easy matter. For
medium-to-large applications, the problem is made even more challenging: here
name conflicts, and lack of synonyms to designate distinct but similar concepts
add to the degree of difficulty.
Using a naming convention can lessen the mental effort required for inventing
suitable names. Aside from this benefit, a naming convention has the added
benefit of enforcing consistency in the code. To be useful, a naming convention
should provide guidance on: typographical style (or how to write the names); and
name construction (or how to choose names).
Choose
a naming convention and apply it consistently
It is not so important which naming convention is used as long as it is
applied consistently. Uniformity in naming is far more important than the actual
convention: uniformity supports the principle of minimal surprise.
Because C++ is a case-sensitive language, and because a number of distinct
naming conventions are in widespread use by the C++ community; it will rarely be
possible to achieve absolute naming consistency. We recommend picking a naming
convention for the project based upon the host environment (e.g., UNIX or
Windows) and the principle libraries used by the project; to maximize the code
consistency:
- UNIX-hosted projects that don't make much use of commercial libraries
(e.g., the X Window library, X Toolkit Intrinsics and Motif) may prefer to
use an all lower-case, underscore-separated-word convention: this is the
convention used for UNIX system calls and also by the C++ draft standard
working paper.
- UNIX-hosted projects that are centered around commercial libraries may
prefer to use a capitalized style, also commonly referred to as the
Smalltalk style-a style where the initial letter of words are capitalized,
and the words are concatenated together without separators.
- Microsoft®Windows-based projects may elect to use the unusual
Microsoft®"Hungarian" notation. We, don't recommend this style;
however, as it is contrary to the fundamental principles underlying the
guidelines in this text.
Notes
The careful reader will observe that the examples in this text currently do
not follow all the guidelines. This is due in part to the fact that examples are
derived from multiple sources; and also due to the desire to conserve paper,
therefore the formatting guidelines have not been meticulously applied. But the
message is "do as I say, not as do".
Never
declare names beginning with one or more underscores ('_')
P>Names with a single leading underscore ('_') are often used by library
functions ("_main " and "_exit ").
Names with double leading underscores ("__"); or a single leading
underscore followed by a capital letter are reserved for compiler internal use.
Also avoid names with adjacent underscores, as it is often difficult to discern
the exact number of underscores.
Avoid
using type names that differ only by letter case
It is hard to remember the differences between type names that differ only by
letter case, and thus easy to get confused between them.
Avoid
the use of abbreviations
Abbreviations may be used if they are either commonly used in the application
domain (e.g., FFT for Fast Fourier Transform), or they are defined in a
project-recognized list of abbreviations. Otherwise, it is very likely that
similar but not quite identical abbreviations will occur here and there,
introducing confusion and errors later (e.g., track_identification being
abbreviated trid, trck_id, tr_iden, tid, tr_ident, and so on).
Avoid
the use of suffixes to denote language constructs
The use of suffixes for categorizing kinds of entities (such as type
for type, and error for exceptions) is usually not very effective for
imparting understanding of the code. Suffixes such as array and struct
also imply a specific implementation; which, in the event of an implementation
change-changing the representation from a struct or array-would either have an
adverse effect upon any client code, or would be misleading.
Suffixes can however be useful in a number of limited situations:
- when the choice of appropriate identifiers is very limited; give the best
name to the object and use a suffix for the type;
- when it represents an application-domain concept, e.g., aircraft_type.
Choose
clear, legible, meaningful names
Choose names from the usage perspective; and use adjectives with nouns to
enhance local (context specific) meaning. Also make sure that names agree with
their types.
Choose names so that constructs such as:
object_name.function_name(...);
object_name->function_name(...);
are easy to read and appear meaningful.
Speed of typing is not an acceptable justification for using short or
abbreviated names. One-letter and short identifiers are often an indication of
poor choice or laziness. Exceptions are well-recognized instances such as using E
for the base of the natural logarithms; or Pi.
Unfortunately, compilers and supporting tools, sometimes limit length of names;
thus, care should be taken to ensure that long names do not differ only by their
trailing characters: the differentiating characters may be truncated by these
tools.
Example
void set_color(color new_color)
{
...
the_color = new_color;
...
}
is better than:
void set_foreground_color(color fg)
and:
oid set_foreground_color(color foreground);{
...
the_foreground_color = foreground;
...
}
The naming in the first example is superior to the other two: new_color
is qualified and agrees with its type; thereby strengthening the semantics of
the function.
In the second case, the intuitive reader could infer that fg was
intended to mean foreground; however, in any good programming style, nothing
should be left to reader intuition or inference.
In the third case, when the parameter foreground is used (away from
its declaration), the reader is led to believe that foreground in
fact means foreground color. It could conceivably; however, have been of any
type that is implicitly convertible to a color .
Notes
Forming names from nouns and adjectives, and ensuring that names agree with
their types follows natural-language and enhances both code readability and
semantics.
Use
correct spelling in names
Parts of names that are English words should be spelled correctly and conform
to the project required form, i.e., consistently English or American, but not
both. This is equally true for comments.
Use
positive predicate clauses for Booleans
For Boolean objects, functions and function arguments, use a predicate clause
in the positive form, e.g., found_it , is_available ,
but not is_not_available .
Rationale
When negating predicates, double negatives are harder to understand.
Namespaces
Use
namespaces to partition potential global names by subsystems or by libraries
If a system is decomposed into subsystems, use the subsystem names as
namespace names for partitioning and minimizing the system's global namespace.
If the system is a library, use a single outer-most namespace for the whole
library.
Give each subsystem or library namespace a meaningful name; in addition give it
an abbreviated or acronym alias. Choose abbreviated or acronym aliases that are
unlikely to clash, e.g. the ANSI C++ draft standard library [Plauger,
95] defines std as the alias for iso_standard_library .
If the compiler doesn't yet support the namespace construct, use name prefixes
to simulate namespaces. For example, the public names in the interface of a
system management subsystem could be prefixed with syma (short for System
Management).
Rationale
Using namespaces to enclose potentially global names, helps to avoid name
collisions when code is developed independently (by sub-project teams or
vendors). A corollary is that only namespace names are global.
Classes
Use
nouns or noun phrases for class names
Use a common noun or noun phrase in singular form, to give a class a name
that expresses its abstraction. Use more general names for base classes and more
specialized names for derived classes.
typedef ... reference; // From the standard library
typedef ... pointer; // From the standard library
typedef ... iterator; // From the standard library
class bank_account {...};
class savings_account : public bank_account {...};
class checking_account : public bank_account {...};
When there is a conflict or shortage of suitable names for both objects and
types; use the simple name for the object, and add a suffix such as mode,
kind, code, and so on for the type name.
Use a plural form when expressing an abstraction that represents a collection of
objects.
typedef some_container<...> yellow_pages;
When additional semantics is required beyond just a collection of objects,
use the following from the standard library as behavioral patterns and name
suffixes:
- vector-a randomly accessible sequence container;
- list-an ordered sequence container;
- queue-a first-in-first-out sequence container;
- deque-a double-ended queue;
- stack-a last-in-first-out sequence container;
- set-a key-accessed (associative) container;
- map-a key-accessed (associative) container;
Functions
Use
verbs for procedure-type function names
Use verbs or action phrases for functions that don't have return values
(function declarations with a void return type), or functions that return values
by pointer or reference parameters.
Use nouns or substantives for functions that return only a single value by a
non-void function return type.
For classes with common operations (a pattern of behavior), use operation names
drawn from a project list of choices. For example: begin, end, insert, erase
(container operations from the standard library).
Avoid "get" and "set" naming mentality (prefixing functions
with the prefixes "get" and "set"), especially for public
operations for getting and setting object attributes. Operation naming should
stay at the class abstraction and provision of service level; getting and
setting object attributes are low-level implementation details that weaken
encapsulation if made public.
Use adjectives (or past participles) for functions returning a Boolean
(predicates). For predicates, it is often useful to add the prefix is or has
before a noun to make the name read as a positive assertion. This is also useful
when the simple name is already used for an object, type name, or an enumeration
literal. Be accurate and consistent with respect to tense.
Example
void insert(...);
void erase(...);
Name first_name();
bool has_first_name();
bool is_found();
bool is_available();
Don't use negative names as this can result in expressions with double
negations (e.g., !is_not_found ); making the code more difficult to
understand. In some cases, a negative predicate can also be made positive
without changing its semantics by using an antonym, such as "is_invalid "
instead of "is_not_valid ".
Example
bool is_not_valid(...);
void find_client(name with_the_name, bool& not_found);
Should be re-defined as:
bool is_valid(...);
void find_client(name with_the_name, bool& found);
Use
function overloading when the same general meaning is intended
When operations have the same intended purpose, use overloading rather than
trying to find synonyms: this minimizes the number of concepts and variations of
operations in the system, and thereby reduce its overall complexity.
When overloading operators, ensure that the semantics of the operator are
preserved; if the conventional meaning of an operator cannot be preserved,
choose another name for the function rather than overload the operator.
Objects and Function Parameters
Augment
names with grammatical elements to emphasize meaning
To indicate uniqueness, or to show that this entity is the main focus of the
action, prefix the object or parameter name with "the " or
"this ". To indicate a secondary, temporary, auxiliary
object, prefix it with "a " or "current ":
Example
void change_name( subscriber& the_subscriber,
const subscriber::name new_name)
{
...
the_subscriber.name = new_name;
...
}
void update(subscriber_list& the_list,
const subscriber::identification with_id,
structure& on_structure,
const value for_value);
void change( object& the_object,
const object using_object);
Exceptions
Choose
exception names with a negative meaning
Since exceptions must be used only to handle error situations, use a noun or
a noun phrase that clearly conveys a negative idea:
overflow, threshold_exceeded, bad_initial_value
Use
project defined adjectives for exception names
Use one of the words such as bad, incomplete, invalid, wrong, missing,
or illegal from a project agreed list as part of the name rather than
systematically using error or exception, which do not convey
specific information.
Miscellaneous
Use
capital letters for floating point exponent and hexadecimal digits.
The letter 'E' in floating-point literals and the hexadecimal digits 'A' to
'F' should always be uppercase.
Chapter 5
Declarations
This chapter provides guidance on the usage and form of various C++
declaration kinds.
Namespaces
Prior to the existence of the namespace feature in the C++ language, there
were only limited means to manage name scope; consequently, the global namespace
became rather over-populated, leading to conflicts that prevented some libraries
from being used together in the same program. The new namespace language feature
solves the global namespace pollution problem.
Limit
global declarations to just namespaces
This means that only namespace names may be global; all other declarations
should be within the scope of some namespace.
Ignoring this rule may eventually lead to name collision.
Use
a namespace to group non-class functionality
For logical grouping of non-class functionality (such as a class category),
or for functionality with much greater scope than a class, such as a library or
a subsystem; use a namespace to logical unify the declarations (see "Use
namespaces to partition potential global names by subsystems or by
libraries").
Express the logical grouping of functionality in the name.
Example
namespace transport_layer_interface { /* ... */ };
namespace math_definitions { /* ... */ };
Minimize
the use of global and namespace scope data
The use of global and namespace scope data is contrary to the encapsulation
principle.
Classes
Classes are the fundamental design and implementation unit in C++. They
should be used to capture domain and design abstractions, and as an
encapsulation mechanism for implementing Abstract Data Types (ADT).
Use
class rather than struct for implementing abstract
data types
Use the class class-key rather than struct for
implementing a class-an abstract data type.
Use the struct class-key for defining plain-old-data-structures
(POD) as in C, especially when interfacing with C code.
Although class and struct are equivalent and can be
used interchangeably, class has the preferred default access
control emphasis (private) for better encapsulation.
Rationale
Adopting a consistent practice for distinguishing between class
and struct introduces a semantic distinction above and beyond the
language rules: the class become the foremost construct for
capturing abstractions and encapsulation; whilst the struct
represents a pure data structure that can be exchanged in mixed programming
language programs.
Declare
class members in order of decreasing accessibility
The access specifiers in a class declaration should appear in the order public,
protected, private.
Rationale
The public, protected, private ordering of member declarations ensures that
information of most interest to the class user is presented first, hence
reducing the need for the class user to navigate through irrelevant, or
implementation details.
Avoid
declaring public or protected data members for abstract data types
The use of public or protected data members reduces a class' encapsulation
and affects a system's resilience to change: public data members expose a class'
implementation to its users; protected data members expose a class'
implementation to its derived classes. Any change to the class' public or
protected data members will have consequences upon users and derived classes.
Use
friends to preserve encapsulation
This guideline appears counter-intuitive upon first encounter: friendship
exposes ones private parts to friends, so how can it preserve encapsulation? In
situations where classes are highly interdependent, and require internal
knowledge of each other, it is better to grant friendship rather than exporting
the internal details via the class interface.
Exporting internal details as public members gives access to class clients which
is not desirable. Exporting protected members gives access to potential
descendants, encouraging a hierarchical design which is also not desirable.
Friendship grants selective private access without enforcing a subclassing
constraint, thus preserving encapsulation from all but those requiring access.
A good example of using friendship to preserve encapsulation is granting
friendship to a friend test class. The friend test class, by seeing the class
internals can implement the appropriate test code, but later on, the friend test
class can be dropped from the delivered code. Thus, no encapsulation is lost nor
is coded added to the deliverable code.
Avoid
providing function definitions in class declarations
Class declarations should contain only function declarations and never
function definitions (implementations).
Rationale
Providing function definitions in a class declaration pollutes the class
specification with implementation details; making the class interface less
discernible and more difficult to read; and increases compilation dependencies.
Function definitions in class declarations also reduce control over function
inlining (see also "Use a No_Inline conditional compilation
symbol to subvert inline compilation").
Always
provide a default constructor for classes with explicitly-declared constructors
To allow the use of a class in an array, or any of the STL containers; a
class must provide a public default constructor, or allow the compiler to
generate one.
Notes
An exception to the above rule exists when a class has a non-static data
member of reference type, in this case it is often not possible to create a
meaningful default constructor. It is questionable; therefore, to use a
reference to an object data member.
Always
declare copy constructors and assignment operators for classes with pointer type
data members
If needed, and not explicitly declared, the compiler will implicitly generate
a copy constructor and an assignment operator for a class. The compiler defined
copy constructor and assignment operator implement what is commonly referred to
in Smalltalk terminology as "shallow-copy": explicitly, memberwise
copy with bitwise copy for pointers. Use of the compiler generated copy
constructor and default assignment operators is guaranteed to leak memory.
Example
// Adapted from [Meyers, 92].
void f()
{
String hello("Hello");// Assume String is implemented
// with a pointer to a char
// array.
{ // Enter new scope (block)
String world("World");
world = hello; // Assignment loses world's
// original memory
} // Destruct world upon exit from
// block;
// also indirectly hello
String hello2 = hello; // Assign destructed hello to
// hello2
}
In the above code, the memory holding the string "World "
is lost after the assignment. Upon exiting the inner block, world
is destroyed; thus, also losing the memory referenced by hello . The
destructed hello is assigned to hello2 .
Example
// Adapted from [Meyers, 1992].
void foo(String bar) {};
void f()
{
String lost = "String that will be lost!";
foo(lost);
}
In the above code, when foo is called with argument lost ,
lost will be copied into foo using the compiler
defined copy constructor. Since lost is copied with a bitwise copy
of the pointer to "String that will be lost!" , upon exit
from foo , the copy of lost will be destroyed (assuming
the destructor is implemented correctly to free up memory) along with the memory
holding "String that will be lost!"
Never
re-declare constructor parameters to have a default value
Example
// Example from [X3J16, 95; section 12.8]
class X {
public:
X(const X&, int); // int parameter is not
// initialized
// No user-declared copy constructor, thus
// compiler implicitly declares one.
};
// Deferred initialization of the int parameter mutates
// constructor into a copy constructor.
//
X::X(const X& x, int i = 0) { ... }
Rationale
A compiler not seeing a "standard" copy constructor signature in a
class declaration will implicitly declare a copy constructor. Deferred
initialization of default parameters may however mutate a constructor into copy
constructor: resulting in ambiguity when a copy constructor is used. Any use of
a copy constructor is thus ill-formed because of the ambiguity [X3J16,
95; section 12.8].
Always
declare destructors to be virtual
Unless a class is explicitly designed to be non-derivable, its destructor
should always be declared virtual.
Rationale
Deletion of a derived class object via a pointer or reference to a base class
type will result in undefined behavior unless the base class destructor has been
declared virtual.
Example
// Bad style used for brevity
class B {
public:
B(size_t size) { tp = new T[size]; }
~B() { delete [] tp; tp = 0; }
//...
private:
T* tp;
};
class D : public B {
public:
D(size_t size) : B(size) {}
~D() {}
//...
};
void f()
{
B* bp = new D(10);
delete bp; // Undefined behavior due to
// non-virtual base class
// destructor
}
Avoid
declaring too many conversion operators and single parameter constructors
Single parameter constructors can also be prevented from being used for
implicit conversion by declaring them with the explicit specifier.
Never
redefine non-virtual functions
Non-virtual functions implement invariant behavior and are not intended to be
specialized by derived classes. Violating this guideline may produce unexpected
behavior: the same object may exhibit different behavior at different times.
Non-virtual functions are statically bound; thus, the function invoked upon an
object is governed by the static type of the variable referencing the
object-pointer-to-A and pointer-to-B respectively in the example below-and not
the actual type of the object.
Example
// Adapted from [Meyers, 92].
class A {
public:
oid f(); // Non-virtual: statically bound
};
class B : public A {
public:
void f(); // Non-virtual: statically bound
};
void g()
{
B x;
A* pA = &x; // Static type: pointer-to-A
B* pB = &x; // Static type: pointer-to-B
pA->f(); // Calls A::f
pB->f(); // Calls B::f
}
Use
non-virtual functions judiciously
Since non-virtual functions constrain subclasses by restricting
specialization and polymorphism, care should be taken to ensure that an
operation is truly invariant for all subclasses before declaring it non-virtual.
Use
constructor-initializers rather than assignments in constructors
The initialization of an object's state during construction should be
performed by a constructor initializer-a member initializer list-rather than
with assignment operators within the constructor body.
Example
Do this:
class X
{
public:
X();
private
Y the_y;
};
X::X() : the_y(some_y_expression) { }
//
// "the_y" initialized by a constructor-initializer
Rather than this:
X::X() { the_y = some_y_expression; }
//
// "the_y" initialized by an assignment operator.
Rationale
Object construction involves the construction of all base classes and data
members prior to the execution of the constructor body. Initialization of data
members requires two operations (construction plus assignment) if performed in a
constructor body as opposed to a single operation (construction with an initial
value) when performed using a constructor-initializer.
For large nested aggregate classes (classes containing classes containing
classes...), the performance overheads of multiple operations-construction +
member assignment-can be significant.
Never
call member functions from a constructor initializer
Example
class A
{
public:
A(int an_int);
};
class B : public A
{
public:
int f();
B();
};
B::B() : A(f()) {}
// undefined: calls member function but A bas
// not yet been initialized [X3J16, 95].
Rationale
The result of an operation is undefined if a member function is called
directly or indirectly from a constructor initializer before all the member
initializers for base classes have completed [X3J16,
95].
Beware
when calling member functions in constructors and destructors
Care should be exercised when calling member functions in constructors; be
aware that even if a virtual function is called, the one that is executed is the
one defined in the constructor or destructor's class or one of its base's.
Use
static const for integral class constants
When defining integral (integer) class constants, use static const
data members rather than #define 's or global constants. If static
const is not supported by the compiler, use enum 's instead.
Example
Do this:
class X {
static const buffer_size = 100;
char buffer[buffer_size];
};
static const buffer_size;
Or this:
class C {
enum { buffer_size = 100 };
char buffer[buffer_size];
};
But not this:
#define BUFFER_SIZE 100
class C {
char buffer[BUFFER_SIZE];
};
Functions
Always
declare an explicit function return type
This will prevent confusion when the compiler complains about a missing
return type for functions declared without an explicit return type.
Always
provide formal parameter names in function declarations
Also use the same names in both function declarations and definitions; this
minimizes surprises. Providing parameter names improves code documentation and
readability.
Strive
for functions with a single point of return
Return statements sprinkled freely over a function body are akin to goto
statements, making the code more difficult to read and to maintain.
Multiple returns can be tolerated only in very small functions, when all return 's
can be seen simultaneously and when the code has a very regular structure:
type_t foo()
{
if (this_condition)
return this_value;
else
return some_other_value;
}
Functions with void return type should have no return statement.
Avoid
creating function with global side-effects
The creation of functions that produce global side-effects (change
unadvertised data other than their internal object state: such as global and
namespace data) should be minimized (see also "Minimize the use of global
and namespace scope data"). But if unavoidable, then any side effects
should be clearly documented as part of the function specification.
Passing in the required objects as parameters makes code less context dependent,
more robust, and easier to understand.
Declare
function parameters in order of decreasing importance and volatility
The order in which parameters are declared is important from the
caller's point of view:
- First define the non-defaulted parameters in order of decreasing
importance;
- Then define the parameters that have default values, with the most likely
to be modified first.
This ordering permits taking advantage of defaults to reduce the number of
arguments in function calls.
Avoid
declaring functions with a variable number of parameters
Arguments for functions with a variable number of parameters cannot be
type-checked.
Avoid
re-declaring functions with default parameters
Avoid adding defaults to functions in further re-declarations of the
function: apart from forward declarations, a function should only be declared
once. Otherwise this may cause confusion for readers who are not aware of
subsequent declarations.
Maximize
the use of const in function declarations
Check whether functions have any constant behavior (return a constant value;
accept constant arguments; or operate without side effect) and assert the
behavior using the const specifier.
Example
const T f(...); // Function returning a constant
// object.
T f(T* const arg); // Function taking a constant
// pointer.
// The pointed-to object can be
// changed but not the pointer.
T f(const T* arg); // Function taking a pointer to, and
T f(const T& arg); // function taking a reference to a
// constant object. The pointer can
// change but not the pointed-to
// object.
T f(const T* const arg); // Function taking a constant
// pointer to a constant object.
// Neither the pointer nor pointed-
// to object may change.
T f(...) const; // Function without side-effect:
// does not change its object state;
// so can be applied to constant
// objects.
Avoid
passing objects by value
Passing and returning objects by value may incur heavy constructor and
destructor overhead. The constructor and destructor overhead can be avoided by
passing and returning objects by reference.
Const references can be used to specify that arguments passed by
reference cannot be modified. Typical usage examples are copy constructors and
assignment operators:
C::C(const C& aC);
C& C::operator=(const C& aC);
Example
Consider the following:
the_class the_class::return_by_value(the_class a_copy)
{
return a_copy;
}
the_class an_object;
return_by_value(an_object);
When return_by_value is called with an_object as
argument, the_class copy constructor is invoked to copy an_object
to a_copy . the_class copy constructor is invoked again
to copy a_copy to the function return temporary object. the_class
destructor is invoked to destroy a_copy upon return from the
function. Some time later the_class destructor will be invoked
again to destroy the object returned by return_by_value . The
overall cost of the above do-nothing function call is two constructors and two
destructors.
The situation is even worse if the_class was a derived class and
contained member data of other classes; the constructors and destructors of base
classes and contained classes would also be invoked, thus escalating the number
of constructor and destructor calls incurred by the function call.
Notes
The above guideline may appear to invite developers to always pass and return
objects by reference, however care should be exercised not to return references
to local objects or references when objects are required. Returning a reference
to local a object is an invitation for disaster since upon function return, the
returned reference is bound to a destroyed object!
Never
return a reference to a local object
Local objects are destroyed upon leaving function scope; using destroyed
objects is inviting disaster.
Never
return a de-referenced pointer initialized by new
Violation of this guideline will lead to memory leaks
Example
class C {
public:
...
friend C& operator+( const C& left,
const C& right);
};
C& operator+(const C& left, const C& right)
{
C* new_c = new C(left..., right...);
return *new_c;
}
C a, b, c, d;
C sum;
sum = a + b + c + d;
Since the intermediate results of the operator+'s are not stored when
computing sum, the intermediate objects cannot be deleted, leading to memory
leaks.
Never
return a non-const reference or pointer to member data
Violation of this guideline violates data encapsulation and may lead to bad
surprises.
Use
inline functions in preference to #define for macro expansion
But use inlining judiciously: only for very small functions; inlining large
functions may cause code bloat.
Inline functions also increase the compilation dependencies between modules, as
the implementation of the inline functions need to be made available for
compilation of the client code.
[Meyers, 1992] provides a detailed discussion of
the following rather extreme example of bad macro usage:
Example
Don't do this:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
Rather, do this:
inline int max(int a, int b) { return a > b ? a : b; }
The macro MAX has a number of problems: it is not type-safe; and
its behavior is non-deterministic:
int a = 1, b = 0;
MAX(a++, b); // a is incremented twice
MAX(a++, b+10); // a is incremented once
MAX(a, "Hello"); // comparing ints and pointers
Use
default parameters rather than function overloading
Use default parameters rather than function overloading when a single
algorithm can be exploited, and the algorithm can be parameterized by a small
number of parameters.
Using default parameters helps to reduce the number of overloaded functions,
enhancing maintainability, and reduces the number of arguments required in
function calls, improving code readability.
Use
function overloading to express common semantics
Use function overloading when multiple implementations are required for the
same semantic operation, but with different argument types.
Preserve conventional meaning when overloading operators. Don't forget to define
related operators, e.g., operator== and operator!= .
Avoid
overloading functions taking pointers and integers
Avoid overloading functions with a single pointer argument by functions with
a single integer argument:
void f(char* p);
void f(int i);
The following calls may cause surprises:
PRE>f(NULL); f(0);
Overload resolution resolves to f(int) and not f(char*) .
Have
operator= return a reference to *this
C++ allows chaining of the assignment operators:
String x, y, z;
x = y = z = "A string";
Since the assignment operator is right-associative, the string "A
string " is assigned to z, z to y, and y to x. The operator=
is effectively invoked once for each expression on the right side of the =, in a
right to left order. This also means that the result of each operator=
is an object, however a return choice of either the left hand or the right hand
object is possible.
Since good practice dictates that the signature of the assignment operator
should always be of the form:
C& C::operator=(const C&);
only the left hand object is possible (rhs is const reference, lhs is
non-const reference), thus *this should be returned. See [Meyers,
1992] for a detailed discussion.
Have
operator= check for self-assignment
There are two good reasons for performing the check: firstly, assignment of a
derived class object involves calling the assignment operator of each base class
up the inheritance hierarchy and skipping these operations may provide
significant runtime savings. Secondly, assignment involves the destruction of
the "lvalue" object prior to copying the "rvalue" object. In
the case of a self assignment, the rvalue object is destroyed before it is
assigned, the result of the assignment is thus undefined.
Minimize
complexity
Do not write overly long functions, for example over 60 lines of code.
Minimize the number of return statements, 1 is the ideal number.
Strive for a Cyclomatic Complexity of less than 10 (sum of the decision
statements + 1, for single exit statement functions).
Strive for an Extended Cyclomatic Complexity of less than 15 (sum of the
decision statements + logical operators + 1, for single exit statement
functions).
Minimize the mean maximum span of reference (distance in lines between the
declaration of a local object and the first instance of its use).
Types
Define
project-wide global system types
In large projects there are usually a collection of types used frequently
throughout the system; in this case it is sensible to collect together these
types in one or more low-level global utility namespaces (see example for
"Avoid the use of fundamental types").
Avoid
the use of fundamental types
When a high degree of portability is the objective, or when control is needed
over the memory space occupied by numeric objects, or when a specific range of
values is required; then fundamental types should not be used. In these
situations it is better to declare explicit type names with size constraints
using the appropriate fundamental types.
Make sure that fundamental types don't sneak back into the code through loop
counters, array indices, and so on.
Example
namespace system_types {
typedef unsigned char byte;
typedef short int integer16; // 16-bit signed integer
typedef int integer32; // 32-bit signed integer
typedef unsigned short int natural16; // 16-bit unsigned integer
typedef unsigned int natural32; // 32-bit unsigned integer
...
}
Rationale
The representation of fundamental types is implementation dependent.
Use
typedef to create synonyms to strengthen local meaning
Use typedef to create synonyms for existing names, to give more
meaningful local names and improve legibility (there is no runtime penalty for
doing so).
typedef can also be used to provide shorthands for qualified names.
Example
// vector declaration from standard library
//
namespace std {
template <class T, class Alloc = allocator>
class vector {
public:
typedef typename
Alloc::types<T>reference reference;
typedef typename
Alloc::types<T>const_reference const_reference;
typedef typename
Alloc::types<T>pointer iterator;
typedef typename
Alloc::types<T>const_pointer const_iterator;
...
}
}
When using typedef-names created by typedef , do not mix the use
of the original name and the synonym in the same piece of code.
Constants and Objects
H3>Avoid
using literal values
Use named constants in preference.
Avoid
using the preprocessor #define directive for defining constants
Use const or enum instead.
Don't do this:
#define LIGHT_SPEED 3E8
Rather, do this:
const int light_speed = 3E8;
Or this for sizing arrays:
enum { small_buffer_size = 100,
large_buffer_size = 1000 };
Rationale
Debugging is much harder because names introduced by #defines
are replaced during compilation preprocessing, and do not appear in symbol
tables.
Declare
objects close to their point of first use
Always
initialize const objects at declaration
const objects not declared extern have internal
linkage, initializing these constant objects at declaration allows the
initializers to be used at compilation time.
Never
cast away the "constness" of a constant object
Constant objects may exist in read-only memory.
Initialize
objects at definition
Specify initial values in object definitions, unless the object is
self-initializing. If it is not possible to assign a meaningful initial value,
then assign a "nil" value or consider declaring the object later.
For large objects, it is generally not advisable to construct the objects, and
then later initialize them using assignment as this can be very costly (see also
"Use constructor initializers rather than assignments in
constructors").
If proper initialization of an object is not possible at the time of
construction, then initialize the object using a conventional "nil"
value that means "uninitialized". The nil value is to be used only for
initialization to declare an "unusable but known value" that can be
rejected in a controlled fashion by algorithms: to indicate an uninitialized
variable error when the object is used before proper initialization.
Note that it is not always possible to declare a nil value for all types,
especially modulo types, such as an angle. In this case choose the least likely
value.
Chapter 6
Expressions and Statements
This chapter provides guidance on the usage and form of various kinds of C++
expressions and statements.
Expressions
Use
redundant parentheses to make compound expressions clearer
Avoid
nesting expressions too deeply
The level of nesting of an expression is defined as the number of nested sets
of parentheses required to evaluate an expression from left to right if the
rules of operator precedence were ignored.
Too many levels of nesting make expressions harder to comprehend.
Do
not assume any particular expression evaluation order
Unless evaluation order is specified by an operator (comma operator, ternary
expression, and conjunctions and disjunctions); do not assume any particular
evaluation order; assuming may lead to bad surprises and non-portability.
For example, don't combine the use of a variable in the same statement as an
increment or decrement of the variable.
Example
foo(i, i++);
array[i] = i--;
Use
0 for null pointers rather than NULL
The use of 0 or NULL for null pointers is a highly controversial
topic.
Both C and C++ define any zero-valued constant expression to be interpretable as
a null pointer. Because 0 is difficult to read and the use of literals is highly
discouraged, programmers have traditionally used the macro NULL as
the null pointer. Unfortunately, there is no portable definition for NULL .
Some ANSI C compilers have used (void *)0, but this turns out to be a poor
choice for C++:
char* cp = (void*)0; /* Legal C but not C++ */
Thus any definition of NULL of the form (T*)0, rather than
simply zero, requires a cast in C++. Historically, guidelines advocating the use
of 0 for null pointers, attempted to alleviate the casting requirement and make
code more portable. Many C++ developers however feel more comfortable using NULL
rather than 0, and also argue that most compilers (more precisely, most header
files) nowadays implement NULL as 0.
This guideline rules in favor of 0, since 0 is guaranteed to work irrespective
of the value of NULL , however, due to controversy, this point is
demoted to the level of a tip, to be followed or ignored as seen fit.
Don't
use old-style casting
Use the new casting operators (dynamic_cast, static_cast,
reinterpret_cast, const_cast ) rather than old-style casting.
If you don't have the new cast operators; avoid casting altogether, especially
downcasting (converting a base class object to a derived class object).
Use the casting operators as follows:
dynamic_cast -to cast between members of the same class
hierarchy (subtypes) using run-time type information (run-time type
information is available for classes with virtual functions). Casting
between such classes is guaranteed to be safe.
static_cast -to cast between members of the same class
hierarchy without using run-time type information; so is not guaranteed to
be safe. If the programmer cannot guarantee type-safety, then use
dynamic_cast.
reinterpret_cast -to cast between unrelated pointer types and
integral (integer) types; is unsafe and should only be used between the
types mentioned.
const_cast -to cast away the "constness" of a
function argument specified as a const parameter. Note const_cast
is not intended to cast away the "constness" of an object truly
defined as a const object (it could be in read-only-memory).
Don't use typeid to implement type-switching logic: let the
casting operators perform the type checking and conversion atomically, see [Stroustrup,
1994] for an in-depth discussion.
Example
Don't do the following:
void foo (const base& b)
{
if (typeid(b) == typeid(derived1)) {
do_derived1_stuff();
else if (typeid(b) == typeid(derived2)) {
do_derived2_stuff();
else if () {
}
}
Rationale
Old-style casting defeats the type system and can lead to hard-to-detect bugs
that are not caught by the compiler: the memory management system can be
corrupted, virtual function tables can get trampled on, and non-related objects
can be damaged when the object is accessed as a derived class object. Note that
the damage can be done even by a read access, as non-existent pointers or fields
might be referenced.
New-style casting operators make type conversion safer (in most cases) and more
explicit.
Use
the new bool type for Boolean expressions
Don't use the old-style Boolean macros or constants: there is no standard
Boolean value true; use the new bool type instead.
Never
compare directly against the Boolean value true
Since there was traditionally no standard value for true (1 or ! 0);
comparisons of non-zero expressions to true could fail.
Use Boolean expressions instead.
Example
Avoid doing this:
if (someNonZeroExpression == true)
// May not evaluate to true
Better to do this:
if (someNonZeroExpression)
// Always evaluates as a true condition.
Never
compare pointers to objects not within the same array
The result of such operations are nearly always meaningless.
Always
assign a null pointer value to a deleted object pointer
Avoid disaster by setting a pointer to a deleted object to null: repeated
deletion of a non-null pointer is harmful, but repeated deletion of a null
pointer is harmless.
Always assign a null pointer value after deletion even before a function return,
since new code may be added later.
Statements
Use
an if-statement when branching on Boolean expressions
Use
a switch-statement when branching on discrete values
Use a switch statement rather than a series of "else if" when the
branching condition is a discrete value.
Always
provide a default branch for switch-statements for catching errors
A switch statement should always contain a default branch,and
the default branch should be used for trapping errors.
This policy ensures that when new switch values are introduced, and branches to
handle the new values are omitted, the existing default branch will catch the
error.
Use
a for-statement or a while-statement when a pre-iteration test is required in a
loop
Use a for-statement in preference to a while statement when iteration and
loop termination is based upon the loop counter.
Use
a do-while-statement when a post-iteration test is required in a loop
Avoid
the use of jump statements in loops
Avoid exiting (using break, return or goto ) from
loops other than by the loop termination condition; and pre-maturely skipping to
the next iteration with continue . This reduces the number of flow
of control paths, making code easier to comprehend.
Don't
use the goto -statement
This seems to be a universal guideline.
Avoid
the hiding of identifiers in nested scopes
This may lead to confusion for the readers and potential risks in
maintenance.
Chapter 7
Special Topics
This chapter provides guidance on the topics of memory management and error
reporting.
Memory Management
Avoid
mixing C and C++ memory operations
The C library malloc , calloc and realloc
functions should not be used for allocating object space: the C++ operator new
should be used for this purpose.
The only time memory should be allocated using the C functions is when memory is
to be passed to a C library function for disposal.
Don't use delete to free memory allocated by C functions, or free on objects
created by new.
Always
use delete[] when deleting array objects created by new
Using delete on array objects without the empty brackets ("[]")
notation will result in only the first array element being deleted, and thus
memory leakage.
Error Handling and Exceptions
Because not much experience has been gained using the C++ exception
mechanism, the guidelines presented here may undergo significant future
revision.
The C++ draft standard defines two broad categories of errors: logic errors and
runtime errors. Logic errors are preventable programming errors. Runtime errors
are defined as those errors due to events beyond the scope of the program.
The general rule for use of exceptions is that the system in normal condition
and in the absence of overload or hardware failure should not raise any
exceptions.
Use
assertions liberally during development to detect errors
Use function preconditions and postcondition assertions during development to provide
"drop-dead" error detection.
Assertions provide a simple and useful provisional error detection mechanism
until the final error handling code is implemented. Assertions have the added
bonus of being able to be compiled away using the "NDEBUG"
preprocessor symbol (see "Define the NDEBUG symbol with a specific
value").
The assert macro has traditionally been used for this purpose; however,
reference [Stroustrup, 1994] provides a template
alternative, see below.
Example
template<class T, class Exception>
inline void assert ( T a_boolean_expression,
Exception the_exception)
{
if (! NDEBUG)
if (! a_boolean_expression)
throw the_exception;
}
Use
exceptions only for truly exceptional conditions
Do not use exceptions for frequent, anticipated events: exceptions cause
disruptions in the normal flow of control of the code, making it more difficult
to understand and maintain.
Anticipated events should be handled in the normal flow of control of the code;
use a function return value or "out" parameter status code as
required.
Exceptions should also not be used to implement control structures: this would
be another form of "goto" statement.
Derive
project exceptions from standard exceptions
This ensures that all exceptions support a minimal set of common operations
and can be handled by a small set of high level handlers.
Logic errors (domain error, invalid argument error, length error and
out-of-range error) should be used to indicate application domain errors,
invalid arguments passed to function calls, construction of objects beyond their
permitted sizes, and argument values not within permitted ranges.
Runtime errors (range error and overflow error) should be used to indicate
arithmetic and configuration errors, corrupted data, or resource exhaustion
errors only detectable at runtime.
Minimize
the number of exceptions used by a given abstraction
In large systems, having to handle a large number of exceptions at each level
makes the code difficult to read and to maintain. Exception processing may dwarf
the normal processing.
Ways to minimize the number of exceptions are:
Share exceptions between abstractions by using a small number of exception
categories.
Throw specialized exceptions derived from the standard exceptions but handle
more generalized exceptions.
Add "exceptional" states to the objects, and provide primitives to
check explicitly the validity of the objects.
Declare
all exceptions thrown
Functions originating exceptions (not just passing exceptions through) should
declare all exceptions thrown in their exception specification: they should not
silently generate exceptions without warning their clients.
Report
exceptions at first occurrence
During development, report exceptions by the appropriate logging mechanism as
early as possible, including at the "throw-point".
Define
exception handlers in most-derived, to most-base class order
Exception handlers should be defined in the most-derived, to most-base class
order in order to avoid coding unreachable handlers; see the how-not-to-it
example, below. This also ensures that the most appropriate handler catches the
exception since handlers are matched in a declaration order.
Example
P>Don't do this:
class base { ... };
class derived : public base { ... };
...
try {
...
throw derived(...);
//
// Throw a derived class exception
}
catch (base& a_base_failure)
//
// But base class handler "catches" because
// it matches first!
{
...
}
catch (derived& a_derived_failure)
//
// This handler is unreachable!
{
...
}
Avoid
catch-all exception handlers
Avoid catch-all exception handlers (handler declarations using ...), unless
the exception is re-thrown.
Catch-all handlers should only be used for local housekeeping, then the
exception should be re-thrown to prevent masking of the fact that the exception
cannot be handled at this level:
try {
...
}
catch (...)
{
if (io.is_open(local_file))
{
io.close(local_file);
}
throw;
}
Make
sure function status codes have an appropriate value
When returning status codes as a function parameter, always assigned a value
to the parameter as the first executable statement in the function body.
Systematically make all statuses a success by default or a failure by default.
Think of all possible exits from the function, including exception handlers.
Perform
safety checks locally; do not expect your client to do so
If a function might produce an erroneous output unless given proper input,
install code in the function to detect and report invalid input in a controlled
manner. Do not rely on a comment that tells the client to pass proper values. It
is virtually guaranteed that sooner or later that comment will be ignored,
resulting in hard-to-debug errors if the invalid parameters are not detected.
Chapter 8
Portability
This chapter deals with language features that are a priori
non-portable.
Pathnames
Never
use hardcoded file pathnames
Pathnames are not represented in a standard manner across operating systems.
Using them will introduce platform dependencies.
Example
#include "somePath/filename.hh" // Unix
#include "somePath\filename.hh" // MSDOS
Data Representation
The representation and alignment of types are highly machine architecture
dependent. Assumptions made about representation and alignment may lead to bad
surprises and reduced portability.
Do
not assume the representation of a type
In particular, never attempt to store a pointer in an int , a
long or any other numeric type-this is highly non-portable.
Do
not assume the alignment of a type
Do
not depend on a particular underflow or overflow behavior
Use
"stretchable" constants whenever possible
Stretchable constants avoid problems with word-size variations.
Example
const int all_ones = ~0;
const int last_3_bits = ~0x7;
Type Conversions
Do
not convert from a "shorter" type to a "longer" type
Machine architectures may dictate the alignment of certain types. Converting
from types with more relaxed alignment requirements to types with more stringent
alignment requirements may lead to program failures.
Chapter 9
Reuse
This chapter provides guidance on reusing C++ code.
Use
standard library components whenever possible
If the standard libraries are not available, then create classes based upon
the standard library interfaces: this will facilitate future migration.
Use
templates to reuse data independent behavior
Use templates to reuse behavior, when behavior is not dependent upon a
specific data type.
Use
public inheritance to reuse class interfaces (subtyping)
Use public inheritance to express the "isa"
relationship and reuse base class interfaces, and optionally, their
implementation.
Use
containment rather than private inheritance to reuse class implementations
Avoid private inheritance when reusing implementation or modeling
"parts/whole" relationships. Reuse of implementation without
redefinition is best achieved by containment rather than by private inheritance.
Use private inheritance when redefinition of base class operations is needed.
Use
multiple inheritance judiciously
Multiple inheritance should be used judiciously as it brings much additional
complexity. [Meyers, 1992] provides a detailed
discussion on the complexities due to potential name ambiguities and repeated
inheritance. Complexities arise from:
Ambiguities, when the same names are used by multiple classes, any unqualified
references to the names are inherently ambiguous. Ambiguity can be resolved by
qualifying the member names with their class names. However, this has the
unfortunate effect of defeating polymorphism and turning virtual functions into
statically bound functions.
Repeated inheritance (inheritance of a base class multiple times by a derived
class via different paths in the inheritance hierarchy) of multiple sets of data
members from the same base raises the problem of which of the multiple sets of
data members should be used?
Multiply inherited data members can be prevented by using virtual inheritance
(inheritance of virtual base classes). Why not always use virtual inheritance
then? Virtual inheritance has the negative effect of altering the underlying
object representation and reducing access efficiency.
Enacting a policy to require all inheritance to be virtual, at the same time
imposing an all encompassing space and time penalty, would be too authoritarian.
Multiple inheritance; therefore, requires class designers to be clairvoyant as
to the future uses of their classes: in order to be able to make the decision to
use virtual or non-virtual inheritance.
Chapter 10
Compilation Issues
This chapter provides guidance on compilation issues
Minimize
compilation dependencies
Do not include in a module specification other header files that are only
required by the module's implementation.
Avoid including header files in a specification for the purpose of gaining
visibility to other classes, when only pointer or reference visibility is
required; use forward declarations instead.
Example
// Module A specification, contained in file "A.hh"
#include "B.hh" // Don't include when only required by
// the implementation.
#include "C.hh" // Don't include when only required by
// reference; use a forward declaration instead.
class C;
class A
{
C* a_c_by_reference; // Has-a by reference.
};
// End of "A.hh"
Notes
Minimizing compilation dependencies is the rationale for certain design
idioms or patterns, variously named: Handle or Envelope [Meyers,
1992], or Bridge [Gamma] classes. By dividing the responsibility for a class
abstraction across two associated classes, one providing the class interface,
and the other the implementation; the dependencies between a class and its
clients are minimized since any changes to the implementation (the
implementation class) no longer cause recompilation of the clients.
Example
// Module A specification, contained in file "A.hh"
class A_implementation;
class A
{
A_implementation* the_implementation;
};
// End of "A.hh"
This approach also allows the interface class and implementation class to be
specialized as two separate class hierarchies.
Define
the NDEBUG symbol with a specific value
The NDEBUG symbol was traditionally used to compile away
assertion code implemented using the assert macro. The traditional usage
paradigm was to define the symbol when it was desired to eliminate assertions;
however, developers were often unaware of the presence of assertions, and
therefore never defined the symbol.
We advocate using the template version of the assert; in this case the NDEBUG
symbol has to be given an explicit value: 0 if assertion code is desired;
non-zero to eliminate. Any assertion code subsequently compiled without
providing the NDEBUG symbol a specific value will generate
compilation errors; thus, bringing the developer's attention to the existence of
assertion code.
Guideline Summary
Here is a summary of all the guidelines presented in this booklet.
Requirements
or Restrictions
Use common sense
Always use #include to gain access to a
module's specification
Never declare names beginning with one or more
underscores ('_')
Limit global declarations to just namespaces
Always provide a default constructor for classes with
explicitly-declared constructors
Always declare copy constructors and assignment
operators for classes with pointer type data members
Never re-declare constructor parameters to have a
default value
Always declare destructors to be virtual
Never redefine non-virtual functions
Never call member functions from a constructor
initializer
Never return a reference to a local object
Never return a de-referenced pointer initialized by new
Never return a non-const reference or pointer to member
data
Have operator= return a reference to *this
Have operator= check for self-assignment
Never cast away the "constness" of a constant
object
Do not assume any particular expression evaluation order
Don't use old-style casting
Use the new bool type for Boolean
expressions
Never compare directly against the Boolean value true
Never compare pointers to objects not within the same
array
Always assign a null pointer value to a
deleted object pointer
Always provide a default branch for switch-statements
for catching errors
Don't use the goto-statement
Avoid mixing C and C++ memory operations
Always use delete[] when deleting array objects created
by new
Never use hardcoded file pathnames
Do not assume the representation of a type
Do not assume the alignment of a type
Do not depend on a particular underflow or overflow
behavior
Do not convert from a "shorter" type to a
"longer"
Define the NDEBUG symbol with a specific
value
Recommendations
Place module specifications and implementations in
separate files
Pick a single set of file name extensions to distinguish
headers from implementation files
Avoid defining more than one class per module
specification
Avoid putting implementation-private declarations in
module specifications
Place module inline function definitions in a separate
file
Break large modules into multiple translation units if
program size is a concern
Isolate platform dependencies
Protect against repeated file inclusions
Use a "No_Inline " conditional
compilation symbol to subvert inline compilation
Use a small, consistent indentation style for nested
statements
Indent function parameters from the function name or
scope name
Use a maximum line length that would fit on the standard
printout paper size
Use consistent line folding
Use C++ style comments rather than C-style comments
Maximize comment proximity to source code
Avoid end of line comments
Avoid comment headers
Use an empty comment line to separate comment paragraphs
Avoid redundancy
Write self-documenting code rather than comments
Document classes and functions
Choose a naming convention and apply it consistently
Avoid using type names that differ only by letter case
Avoid the use of abbreviations
Avoid the use of suffixes to denote language constructs
Choose clear, legible, meaningful names
Use correct spelling in names
Use positive predicate clauses for Booleans
Use namespaces to partition potential global names by
subsystems or by libraries
Use nouns or noun phrases for class names
Use verbs for procedure-type function names
Use function overloading when the same general meaning
is intended
Augment names with grammatical elements to emphasize
meaning
Choose exception names with a negative meaning
Use project defined adjectives for exception names
Use capital letters for floating point exponent and
hexadecimal digits.
Use a namespace to group non-class functionality
Minimize the use of global and namespace scope data
Use class rather than struct for implementing abstract
data types
Declare class members in order of decreasing
accessibility
Avoid declaring public or protected data members for
abstract data types
Use friends to preserve encapsulation
Avoid providing function definitions in class
declarations
Avoid declaring too many conversion operators and single
parameter constructors
Use non-virtual functions judiciously
Use constructor-initializers rather than assignments in
constructors
Beware when calling member functions in constructors and
destructors
Use static const for integral class constants
Always declare an explicit function return type
Always provide formal parameter names in function
declarations
Strive for functions with a single point of return
Avoid creating function with global side-effects
Declare function parameters in order of decreasing
importance and volatility
Avoid declaring functions with a variable number of
parameters
Avoid re-declaring functions with default parameters
Maximize the use of const in function declarations
Avoid passing objects by value
Use inline functions in preference to #define
for macro expansion
Use default parameters rather than function overloading
Use function overloading to express common semantics
Avoid overloading functions taking pointers and integers
Minimize complexity
Avoid the use of fundamental types
Avoid using literal values
Avoid using the preprocessor #define
directive for defining constants
Declare objects close to their point of first use
Always initialize const objects at declaration
Initialize objects at definition
Use an if-statement when branching on Boolean
expressions
Use a switch-statement when branching on discrete values
Use a for-statement or a while-statement when a
pre-iteration test is required in a loop
Use a do-while-statement when a post-iteration test is
required in a loop
Avoid the use of jump statements in loops
Avoid the hiding of identifiers in nested scopes
Use assertions liberally during development to detect
errors
Use exceptions only for truly exceptional conditions
Derive project exceptions from standard exceptions
Minimize the number of exceptions used by a given
abstraction
Declare all exceptions thrown
Define exception handlers in most-derived, to most-base
class order
Avoid catch-all exception handlers
Make sure function status codes have an appropriate
value
Perform safety checks locally; do not expect your client
to do so
Use "stretchable" constants whenever possible
Use standard library components whenever possible
Tips
Define project-wide global system types
Use typedef to create synonyms to strengthen local
meaning
Use redundant parentheses to make compound expressions
clearer
Avoid nesting expressions too deeply
Use 0 for null pointers rather than NULL
Report exceptions at first occurrence
[Cargill, 92] |
Cargill, Tom. 1992. C++ Programming Styles
Addison-Wesley.
|
|
[Coplien, 92] |
Coplien, James O. 1992. Advanced C++
Programming Styles and Idioms, Addison-Wesley.
|
|
[Ellemtel, 93] |
Ellemtel Telecommunications Systems
Laboratories. June 1993. Programming in C++ Rules and Recommendations.
|
|
[Ellis, 90] |
Ellis, Margaret A. and Stroustrup,
Bjarne.1990. The Annotated C++ Reference Manual, Addison-Wesley.
|
|
[Kruchten, 94] |
Kruchten, P. May 1994. Ada Programming
Guidelines for the Canadian Automated Air Traffic System.
|
|
[Lippman, 96] |
Lippman, Stanley, B. 1996. Inside the C++
Object Model, Addison-Wesley.
|
|
[Meyers, 92] |
Meyers, Scott. 1992. Effective C++,
Addison-Wesley.
|
|
[Meyers, 96] |
Meyers, Scott. 1996. More Effective C++,
Addison-Wesley.
|
|
[Plauger, 95] |
Plauger, P.J. 1995. The Draft Standard C++
Library, Prentice Hall, Inc.
|
|
[Plum, 91] |
Plum, Thomas and Saks, Dan. 1991. C++
Programming Guidelines, Plum Hall Inc.
|
|
[Stroustrup, 94] |
Stroustrup, Bjarne. 1994. The Design and
Evolution of C++, Addison-Wesley.
|
|
[X3J16, 95] |
X3J16/95-0087 | WG21/N0687. April 1995.
Working Paper for Draft Proposed International Standard for Information
Systems-Programming Language C++. |
Copyright
© 1987 - 2001 Rational Software Corporation
|