Pace University, 1997
A rule marked with (!) should be ignored only at your peril. One marked with (?) should generally be obeyed, but there may be reasons for ignoring it.
(!)If a class manages memory dynamically ( calls new to create member variables ) then it needs all of the following
A default constructor -- one that can be called with no parameters. A copy constructor A destructor An overloaded operator=
Actually, if a class defines any of these or any constructor, then it probably needs all of these. Likewise if it has any pointer or array member variables.
A constructor with a single argument other than the copy constructor should be marked explicit unless there is a reason for not doing so. This avoids automatic type conversion that can sometimes surprise you.
(!)If a class has any virtual methods then it needs a virtual destructor, even if that destructor does nothing. Otherwise the wrong destructor may be called in some contexts.
A parameter of class type passed in to a function by value would probably be better passed as a const reference. This avoids construction and destruction of temp objects. An exception to this rule occurs when the function will somehow "consume" the argument.
In particular, any operator<< defining output on a stream should have its second argument passed as a const reference.
Avoid private inheritance. Use a member variable instead.
(?)Avoid multiple inheritance for most uses.
(!) Do not mix iostream i/o with older C based i/o. They are both buffered, but use different buffers. In particular using cout and stdout together is likely to mix output in unpredictable ways.
The last case in a switch should be terminated with a break. Consider listing the default case first. It does not need to be listed last, contrary to the impressions of some. If some branch of a switch should not terminate in a break, document it carefully.
Global data and functions defined in an implementation part, but not in the corresponding header should be marked static. This avoids link problems if different variables or functions with the same names appear in other compilation units.
Avoid use of the C preprocessor. Use const rather than #define to define constant values. Do not use the macro facility, which avoids the type constraints of the language. Allowable uses are #include, and #define for conditional compilation. You can also enclose a block of code in
#if 0
...
#endif
to
effectively comment it out; even if it contains comments.
(!)A header named foo.h should begin with
#ifndef FOO_H
#define FOO_H
and
it should end with
#endif
A variable defined in a header should be marked extern. It should then be defined (without extern) in the corresponding implementation file. This avoids linker problems.
Consider defining one class per header. If classes are closely related they might be defined in the same header, but consider nesting one inside the other. For example, a Node class associated with a List class is best built as a nested class within the List class.
(?)Use inheritance only when the derived type is a specialization of its base type. Use member variables to implement other relationships.
Avoid public member variables.
(!)Never return a reference or a pointer to a local variable from within a function. Return a copy instead.
(!)Don't return a reference to a newly created heap variable. There is no way to deallocate that space later. Return a pointer instead.
SPECIAL TECHNIQUES
An enumeration defined within a class or struct does not leave the names of the constants in the global name space, avoiding name conflicts. A struct might be defined for no other purpose than to package the names.
struct color
{ enum {red, green, blue}
}
Now
use color::red, color::green... Note that the enum itself needs no name. In particular, C++ won't let you define a constant within a class. But you can define an enum within the class and define the constant within the enum.
class stack
{ public:
const int max = 100; // ILLEGAL
...
}
class stack
{public:
enum {max = 100}; // OK
}
STYLE
The public interface should appear at the beginning of the class definition. The private fields should appear at the end of the class definition.
The more global a name, the more descriptive should be its name.
All grouping symbols, {}, (), etc. should either line up horizontally, or vertically.
If you have to break a long statement into several lines, try to have each new line begin with an operator symbol.
Avoid unnecessary levels of indentation. But use indentation to show structure. Avoid excessively deep indentation (8 characters). Save your horizontal real estate.
class base
{ public:
base (){ . . .}
base& operator=(const base& b)
{ if(...)
{
b...
}
else
{
. . .
}
}
. . .
private:
int temp;
. . .
};
Stick to a style once you develop it.
Learn to use dynamic_cast, const_cast, reinterpret_cast, and static_cast and use them in place of old C casting. --> Explanation of the C++ cast operators by G. Bowden Wise
Learn the various (5) uses of const and use them.
(1) Define a constant value
const int i = 10; // must initialize constants
(2) Make the thing the pointer or reference points to unchangeable.
(pointer to const or reference to const)
const int * p;
const int &r = i; // must initialize reference variables
(3) Make a pointer unchangeable (const pointer. points to only one place)
int * const cp = &i; // must initialize
// Note that *cp can change but not cp itself.
(4) Mark a member function that will not change member variables
int get() const;
// fields of "this" will not be modified(unless marked mutable)
(5) Indicate that a reference parameter will not be modified.
void(const foo & x);
// x will not be modified.
You can combine the second and third usages to get a const pointer to a const:
const int *const cpc = &i;
THINGS TO REMEMBER
Remember that a template is not compiled. Only the instantiation is compiled. You don't get a check on syntax of a template definition until you instantiate it. Since a good compiler will avoid compiling parts of a template that aren't actually used, you might not get a full check of syntax errors unless you do a complete test of the template instantiation.
Remember that you can't split a template definition into a header and an implementation with the idea that the implementation will be separately compiled and only the header need be included where needed. This is a consequence of the above. All code defining the template must be in the included file.
Remember that the template argument in a function template must appear as an argument of the function. The return type doesn't count.
When you define overloaded operators, remember that they keep their associativity and precedence. Sometimes this means that a desirable operator based on the symbols that define it would be inappropriate and confusing in its usage. In particular note that operator<< and operator>> have middle level precedence (between the arithmetic and the comparison operators). The assignment operators and operator?: have lower precedence than operator<< and operator>> which is sometimes confusing when you are doing input and output. Also, the assignment operators are right associative.
Remember that pointers have no destructors.
Remember that casting can never make an incorrect program correct.
Remember that the compiler believes you when you use casting. Make sure you are right.
Remember that the compiler believes you when you subscript an array.
Sources: For style rules see Mastering Object-Oriented Design in C++ by Cay Horstman, Wiley, 1995.