The following applies to all source code, regardless of the programming language:
Use JavaBeans naming conventions:
Each source file must include introductory information. At a minimum, this should include the file's name, a description of the functionality contained therein, the author's name, the creation date, and any required legalese. In addition, a modification history should be available - if not within the source itself, at least via the source code control system. If it is helpful to reference other source files in the introductory section, do so.
/**
* Filename: util.cpp
* Author: Jim Crossley
* Date: 01/16/96
*
* This file contains the definition of the "copy" function.
* It is meant to provide an example of acceptable commenting
* conventions. For big blocks of comments, the C-style
* multi-line delimiters are convenient. It is visually
* appealing when each line begins with an identical
* sequence of characters.
**/
/////////////////////////////////////////////////////
// It is a common technique to employ the repetitive
// use of the C++ single-line comment operator. This
// style is also acceptable as a preface to a module,
// class, or function.
/////////////////////////////////////////////////////
int copy(char *p, const char *q)
{
int c = 0; // Count the number of characters copied
// Copy the contents of q to p.
// c is assumed to be initialized to zero.
//
while (*p++ = *q++)
{
c++;
}
return c;
}
In general, the single-line comment operator should
be used to describe variable usage or potentially confusing blocks
within a function or class. Comments should either precede a
code block at the same level of indentation, or, for individual
statements, they may appear at the end of the same line. When aligning "end-of-the-same-line comments," one should use spaces rather than tabs.
Use spaces for your indentation.
Curly braces {} should reflect the level of indentation
of the code block. In general, each brace should appear on a
line by itself. (A "do-while" loop might be a valid
exception to this rule, however.) See the above code fragment
for an example.
Beware of code changes that make existing comments obsolete: bad comments can be worse than none at all. Try to use the language itself to convey what you're trying to express with a comment. At times, it is useful to leave old comments (with appropriate warnings) so that future maintainers will understand the evolution of the current implementation.
Spacing around operators, variable declarations,
and between function parameters should be appropriate and consistent.
In general, operators should be bordered on either side by whitespace.
Exceptions to this include the scope and member access operators
(:: -> .). Variable declarations may include the tab character
for enhanced readability. Avoid declaring multiple variables
on one line. In general, declarations for pointer and reference
variables should be bordered on only one side by whitespace.
As to which side that is, it is this author's opinion that convincing
arguments could be made for either. Be consistent.
#include statements should be logically grouped together near the top of the file, after the introductory comments. Separate "system includes" <file.h> from "library specific includes" from "application specific includes." The latter two will use the "file.h" notation. Avoid explicit pathnames, but you might use relative pathnames to logically organize related groups of header files.
Generally, header files (*.h) should contain type definitions and function declarations. Definitions of data (except const data) and functions should not be included in a header file. Of course, inline functions may appear in a header file, but you might even consider a (*.i) file instead. Good judgment should dictate whether multiple class declarations should appear in one header file: if the classes might be used independently of each other, declare them separately. Similar judgment is required and expected for the organization of method definitions: if methods from multiple classes are presented in one source file, appropriate documentation should be included in the introductory comments.
Use "header file sentries" to guard against name collisions resulting from #includeing the same file twice. Use a consistent naming convention: for a file named "util.h", you might call your sentry, __UTIL_H__.
#ifndef __UTIL_H__ // first non-comment line of the header file #define __UTIL_H__ // second line // body of header file #endif // __UTIL_H__ // the last line of the file
Use your constructors to initialize your member variables. Do not do any "real" work within a constructor -- only perform actions that cannot fail. If you think for one minute that the classes you create might be used by someone else, define a copy constructor and an assignment operator. If you're absolutely sure your classes will never be used improperly, but you're also anal-retentive - not a bad quality for a C++ programmer - you can declare "dummy" methods to be private.
class X {
...
private:
X(const X&);
X& operator=(const X&);
};
If you think for one minute that anyone might subclass your class, (you've declared virtual methods), be sure that the class' destructor is declared virtual.
Avoid the use of the pointer operator (*) in a function's return value and parameter list: use the reference operator (&) instead. When "big" arguments are passed, avoid copying the object by passing a reference to it. And if it is known that the parameter will not be modified, qualify the type as const. In addition to stronger type checking, this practice may also improve your code's performance: when a compiler doesn't have to worry about a variable changing its value, it can make certain optimizations.
Sometimes it will be necessary to pass arguments and return values as pointers rather than references, most likely when arrays of objects are passed. You should still use the const qualifier judiciously, only now you not only have to worry about the "constancy" of the object being pointed to, but the pointer itself. This is the big difference between pointers and references: once initialized, the latter cannot be changed. The former is subject to the presence of the const qualifier.void f( int n ); // by value; n won't be changed void f( const int n ); // by value; n can't be changed void f( int& n ); // by reference; n may be changed void f( const BigClass& c ); // by reference; c can't be changed void f( BigClass& c ); // by reference; c may be changed
Although there exists a large class of functions for which a Boolean return value is appropriate, one should avoid reporting error conditions through return values when the type of error is important. Consider exploiting the C++ exception handling mechanism instead. Speaking of return values, try to maintain one and only one exit point within a function. Many times, multiple return statements within a function indicate another good opportunity for "throwing exceptions" instead. Resource "clean-up" can also be handled more elegantly through the use of exceptions.char * x = "jim"; // both the object and the pointer may be changed *x = 't'; // ok x = "tim"; // ok const char * x = "jim"; // the pointer may change, but the object may not *x = 't'; // error x = "tim"; // ok char *const x = "jim"; // the object may change, but the pointer may not *x = 't'; // ok x = "tim"; // error const char *const x = "jim"; // neither the pointer nor the object may be changed *x = 't'; // error x = "tim"; // error
Use the new and delete operators instead of the traditional C memory allocation functions: malloc, calloc, realloc, and free.
Avoid the use of #define for constants and macros. If you really think you need #define, first consider using const, template, or inline. Although #define is useful and good in C, it is rarely needed in C++, and in fact compromises some of the power of C++, specifically its static type checking.
Use constants instead of literals wherever possible.
Avoid public class member variables. "Accessor functions" provide for "information hiding" which, according to popular object-oriented opinion, is a good thing. They allow for many practical things, too, including multiple views of object state (there does not necessarily have to be a one-to-one correspondence between a member variable and its accessor), a convenient place to synchronize access to member variables between concurrent processes, and the ability to augment the functionality of an object without changing its public interface. Also, accessor functions should be declared as const so that it is immediately apparent that the state of an object is unchanged as a result of invoking the accessor. For example,
class Heater {
public:
Heater(double t) : temp_(t) {};
double getTemperature() const // object left unchanged
{
return temp_;
}
private:
double temp_;
};
As far as member variables go, use a consistent naming scheme so that they can be quickly recognized as a member variable. Popular conventions include the addition of either an "m_" or "m" prefix, or the addition of a "_" suffix. Whichever you choose, be consistent.
Whenever you assign, initialize, or compare a pointer to the "null pointer", use zero (0) instead of NULL. C++ guarantees that a constant expression that evaluates to zero will produce a pointer distinguishable from a pointer to any object or function.
Try not to rely on data type byte-representations.