Discussion of Specific Guidelines Relation to C++



next up previous
Next: TECHNIQUES AND EXAMPLES Up: GUIDELINES Previous: Discussion of General

Discussion of Specific Guidelines Relation to C++

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.

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.

In addition to the guidelines from [SoH95], the following guidelines are applicable to C++ programming.



next up previous
Next: TECHNIQUES AND EXAMPLES Up: GUIDELINES Previous: Discussion of General



David Binkley
Thu Feb 29 10:02:46 EST 1996