This section discusses those guidelines applicable to specific C++ language
features.
It is worth noting that the length of ``G4.1.6: Minimizing Obscure and Subtle
Programming Constructs'' is an indication of the dangers of using C++.
- G1.1.1 Minimizing Dynamic Memory Allocation.
-
Avoid the use of C's malloc and free.
Instead use (sparingly) C++'s new and delete operators.
Overloading these operators changed dramatically with the new C++
standard [ANS95];
overloading them is dangerous until the definitions and compiler
implementations stabilize.
To avoid memory leaks, a clear understanding of resource acquisition
(allocation and release) is important.
To avoid leaks, all classes should include a destructor that releases any
memory allocated by the class' constructor.
To ensure that destructors are called, class constructors should be declared
~ ~ ~foo::foo(a, b): a(...), b(...) {...}
and not
~ ~ ~foo::foo(a, b) {a = ...; b = ...; ...}
because failure in the constructor after the initialization of b will
call the destructors for a and b in the first definition, but not
in the second;
thus, in the second there is a potential memory leak.
A related example, which ensures that once a separate file is successfully
opened it is closed, is presented in the example in
Section 3.5.
~ Finally, always set a ``new_handler'' using the built-in function
set_new_handler.
The default new_handler terminates the program when it cannot satisfy a
memory request.
Program termination at a critical time may be disastrous.
- G1.2.1 Maximizing Structure.
-
Beyond obvious control-flow structure, this guideline includes structuring the
data (primarily through classes and subclasses).
Many of the precautions and guidelines herein deal with controlling problems
using classes and class hierarchies;
for example see G1.2.6.
- G1.2.2 Minimizing Control Flow Complexity.
-
The use of break and continue in loops should be avoided while
break should always be used in switch statements.
In general, the use of many small functions in object-oriented programs helps
minimize control flow complexity.
- G1.2.3 Initialization of Variables before Use.
-
Initialize all variables at their point of definition.
An array class makes this possible for arrays.
In the absence of such a class, initialize an array in a loop immediately after
its definition.
Finally, class constructors and operator= should initialize all class
attributes.
For constructors, ``call'' attribute's constructors before the body of the
constructor (as in G1.1.1).
It is important to note that C++ defines the order of these calls as the order
in which the attributes are declared in the class and not the order they
appear in the constructor definition.
- G1.2.4 Single Entry and Exit Points in Subprograms.
-
Avoid the use of the catch and throw exception handling mechanism.
They provide (restricted) interprocedural control transfers, which violate
single exit.
(Implementation of these features is patchy from compiler to compiler, which
opens concerns about the correctness of their semantics and implementation).
The use of multiple returns should be avoided in long functions (functions
with 100 or more lines of code);
in small functions the opposite is often true.
- G1.2.5 Minimizing Interface Ambiguities.
-
Avoid the use of varargs and extern ``C'' as neither the type
nor number of parameters can be verified by the compiler.
Have all functions check their parameters for range correctness.
Alternatively make parameters with range restrictions a separate class that
includes the range check.
This requires a single copy of the range check (in the class constructor) and
helps avoid forgotten checks.
Finally, the use of multiple inheritance should be tightly controlled if not
eliminated.
Confusion over which member functions are included in the deriving class
and the use of virtual base classes should be considered before multiple
inheritance is used in safety critical systems.
- G1.2.6 Use of Data Typing.
-
Use class in place of struct as it provides better access control.
Also use a class hierarchy with virtual functions in place of a union as
it provides type checking of the data stored in the ``union''
(see the example in Section 3.7).
(This technique applies to any code that contains a discrete type or kind field.)
Since the fundamental types int, float, and char are not true
classes, their use is restricted in certain contexts (function overloading for
example).
Creating classes for the fundamental types provides access control and also
increases uniformity.
For example, use the classes SafeInt (see the example in
Section 3.1) in
place of int and SafeFloat (see the example in Section
3.2) in place of float.
If necessary for execution speed, these classes can be removed, but only if
profile data indicates such a substitution is warranted.
For example, SafeInt can be replaced with int
using ``typedef int SafeInt;.''
Finally, C++ has a much stronger type system than C;
however, it still includes the ability to type-cast pointers.
This usage should be avoided.
- G1.2.8 Use of Parentheses rather than Default Precedence.
-
In C++, this becomes a particular problem when operators are overloaded
with definitions that do not correspond to the normal definition (see G1.2.13).
Thus, it is not a problem when the complex number class defines operator+
and operator* as add and multiply because they have the
expected precedence in C++.
However, overloading becomes a problem when a real number class defines
operator^ (bitwise exclusive or) as exponentiation because
it has an unexpected precedence:
the expression 6.23^2.0+3.0 is 6.23^(2.0+3.0)
and not the desired (6.23^2.0)+3.0.
Always use parentheses;
do not rely on precedence, especially in the presence of operator overloading.
- G1.2.9 Separating Assignment from Evaluation.
-
Functions should be divided into evaluation functions, which compute
results based on their parameters without modifying them, and update
functions, which may modify their parameters.
Evaluation functions should have all constant (const) parameters;
thus, preventing parameter modification.
Update functions should either update the receiving object or produce a new
object.
Those updating the receiving object should return void;
those producing a new object should return a new value of the same class (or
type) and leave their parameters unchanged.
- G4.1.6 Minimizing Obscure or Subtle Programming Constructs.
-
The following ``laundry list'' addresses common C++ error prone idioms.
-
C++ reference type should be avoided because it allows implicit modification
of referenced variables.
Explicit modification through pointers is preferred since it avoids hidden
implicit changes.
This is especially true when a called function modifies an actual parameter
through a reference formal parameter.
The exception to this is parameters passed by reference to avoid the cost of
copying a large data objects.
Such parameters should be passed as constant references (e.g.,
f(const large_type &)).
Declaring a parameter (const &) preserves the semantics of values
parameter passing, without the cost of copying large data structures.
-
Avoid using default parameters to combine functions.
For example, do not use the single function lookup(char *name,
code=-1), where the value of code determines
whether lookup should fail or add name if it is not found.
Such combinations violate G1.2.9.
-
Avoid complex expressions in a condition.
For example, the expression
``if (i & mask == 0)'' is evaluated as
``if (i & (mask == 0))'' and not as
``if ((i & mask) == 0).'' ~
Replace it with
``long masked_i = i & mask; if (masked_i == 0).''
-
Avoid using operator++ except for v++ where v is a simple variable or
*p where p is an identifier.
In particular, expressions such ``v[i] = i++'' are undefined.
-
Since the default constructor, copy constructor, destructor, and the operators
operator=,
operator&, and operator, (i.e.,
operator<comma>) all have default meanings;
they should be explicitly defined in every class (see the example in
Section 3.3).
To avoid unwanted implicit calls to these constructors and operators,
declare them private.
A technique for providing a replacement ``default'' constructor is given in
the example in Section 3.10.
-
The scope resolution operator :: should be used to explicitly indicate
which of a collection of functions or variables with the same name is being
used.
This includes globals accessed as ::global_variable.
-
Avoid pointers to members.
They unnecessarily complicate the code.
Use virtual functions or redesign.
-
For a C++ member function declared virtual in a base class the keyword
virtual should be used in the definition of the function and all
declarations and definitions of the function in each derived class
even though it is optional.
-
For a class that defines the operators operator->, operator*, and
operator[], ensure the equivalences between ``p->m'',
``(*p).m'', and ``p[0].m''.
This will avoid unexpected errors when programmers assume the equivalence for
classes that do not provide it.
Also for a class that defines the operators operator+, operator+=,
operator++(), and operator++(int), ensure the equivalence of
``x = x + 1'', ``x += 1'', and ``++x'' and their relationship
to ``x++.''
- G4.1.7 Minimizing Dispersion of Related Elements.
-
Classes provide excellent
containers for related elements.
Their use should avoid dispersion.
However, avoid the use of friends.
Their use often indicates an oversight in the analysis or design.
Particularly bad are declarations such as ``class x {friend class y;
...},'' which gives all methods of class y assess to the internal
private attributes of class x.
The example in Section 3.4 illustrates how most
friend declarations can be removed without loss of efficiency.
- G4.1.8 Minimizing Use of Literals.
-
Literals (and #defined constants) should be replaced by identifiers
declared const (or enumerated types for a group of related
constants).
Also replace #defined functions with inline functions.
This allows the compiler to type check expressions and parameters and helps
self document the code.
For literal strings, avoid ``char *p="string"'' as the literal "string" can be changed through the pointer (on some system this causes abnormal
program termination).
Instead use ``const char *p="string"'' or if necessary
``p = new char[sizeof("string")]; strcpy(p, "string").''
- G4.2.1 Minimizing the Use of Global Variables.
-
Limit the visibility of variables and functions.
One way of doing this is to declare local variables only where needed.
The ability in C++ to declare variables anywhere within a block (rather than
just the beginning) allows declarations to be made at their point of use.
Variables local to a loop or branch of a conditional should be declared
within the loop or branch and not be visible to the entire function.
If two functions absolutely must share a variable they should be placed in a
separate file and the variables declared static in the file.
This limits the visibility to the two functions only.
The two could also occupy a common Namespace (part of the new C++
standard [ANS95]).
If sensible, the two functions can be placed in a class and the ``global''
variable made an attribute of the class.
Alternatively, static class attributes are shared by all instances of the
class.
These can be used in place of some global variables.
~ In class declarations, declare all attributes private where possible
and protected where not.
This limits the functions-that-can-change-an-attribute to class members for
private attributes and class or sub-class members for protected
attributes.
Never have public attributes as erroneous values to be assigned to the
attribute from anywhere in the code.
See G2.3.2 and the example in Section 3.8.
- G4.2.2 Minimizing the Complexity Class and Function Interfaces.
-
In function calls, avoid complicated actual parameter expressions.
In classes, include only necessary functionality.
All attributes and functions should be declared private if possible.
If not, then they should be declared protected if possible and, if not,
then they should be declared public.
Private base classes can be used to hide implementation details of a derived
class.
Finally, use const for member functions that do not modify attributes.
One common error in calling functions is to interchange parameters.
The example in Section 3.6 provides an examples of how to
simulate named parameter passing (not call-by-name) in C++ which avoids
this problem.