http://wiki.sipfoundry.org/download/attachments/4883124/sipXecs-cpp-coding-standards.html?version=1&modificationDate=1297549171000
http://wiki.sipfoundry.org/display/sipXecs/Coding+Standards
sipX Project Coding Standards for C, C++, and Java
Actually, a language is characterized not so much by what it lets us program as by what it keeps us from expressing. As E.W. Dijkstra observed, the programmer's most difficult, daily task is to not mess things up. The first and most noble duty of a language is thus to help in this eternal struggle. -- Niklaus Wirth
Last Modified 2005-07-25 15:05:12-0400
NOTE: These coding standards are derived from the coding standards developed for the Vision 2000 CCS project, Todd Hoff's C++ coding standards, and AmbySoft's Java coding standards.
A good developer knows that there is more to development than programming.
A great developer knows that there is more to development than development.
Remember that you write code for three audiences: The computer, for whom it must be correct, your muse, for which it it must be elegant, and the poor schmuck who has to modify it, for whom it must be comprehensible.
-- Daniel R. Killoran
The purpose of these coding standards is to facilitate the maintenance, portability, and reuse of custom C, C++, and Java source code developed by the sipX open source project. These standards were developed from a variety of sources, including other standards documents, software examples from defacto standard language references, and personal experience. As with any guidelines, there will be circumstances where full compliance is not desirable for efficiency, maintainability, or other reasons. In these cases, conformance should not be pursued simply for the sake of meeting the standards.
0.1 When the standards are not followed, it is advisable to add a comment with the reason for non-conformance.
svn:ignore
svn:mime-type
and svn:eol-style
sf:tab
strcasecmp
and strncasecmp
Files should be used to organize related code modules, either at the class (for C++ and Java) or function (for C) level. The following table identifies the contents of individual files for each language:
File contents | C | C++ | Java |
---|---|---|---|
class declaration (header) | n/a | X | n/a |
class definition (source) | n/a | X | X |
main function | X | X | (with primary class) |
function(s) | X | X | n/a |
globals | X | X | n/a |
Source files should contain the following components in the order shown. When creating new source files, developers should use the appropriate template file as a starting point.
File contents | C | C++ | Java |
---|---|---|---|
prolog (incuding copyright) | X | X | X |
package imports | n/a | n/a | X |
system #includes | X | X | n/a |
application #includes | X | X | n/a |
external functions | X | X | n/a |
external variables | X | X | n/a |
constants | X | X | X |
static variable initializations | X | X | X |
class declaration | n/a | n/a | X |
public methods | n/a | X | X |
creators | n/a | X | X |
manipulators | n/a | X | X |
accessors | n/a | X | X |
inquiry | n/a | X | X |
protected methods | n/a | X | X |
private methods | n/a | X | X |
non-testing | n/a | X | X |
testing | n/a | X | X |
functions | X | X | n/a |
Header files should contain the following components in the order shown (note that Java does not use header files). When creating new header files, developers should use the appropriate template file as a starting point.
File contents | C | C++ | Java |
---|---|---|---|
prolog (including copyright) | X | X | n/a |
file guard | X | X | n/a |
system #includes | X | X | n/a |
application #includes | X | X | n/a |
#defines | X | X | n/a |
macros | X | X | n/a |
external functions | X | X | n/a |
external variables | X | X | n/a |
constants | X | X | n/a |
structs | X | X | n/a |
forward declarations | X | X | n/a |
class declaration | n/a | X | n/a |
public methods | n/a | X | n/a |
creators | n/a | X | X |
manipulators | n/a | X | X |
accessors | n/a | X | X |
inquiry | n/a | X | X |
protected methods | n/a | X | n/a |
private methods | n/a | X | n/a |
non-testing | n/a | X | X |
testing | n/a | X | X |
inline method definitions | n/a | X | n/a |
functions | X | X | n/a |
#ifndef _MeaningfulName_h_ // first line of the header file following the prolog #define _MeaningfulName_h_ // second line of the header file following the prolog . . // body of the header file . #endif // _MeaningfulName_h_ // last line of the header file; note comment
C++ (and C) code uses the Doxygen system for generating documentation from header and source files. Doxygen supports a wide range of styles; this section provides recommendations for how it should be used in sipX projects.
Doxygen comments are distinguished from normal comments by an extra comment character at the beginning, and are associated with the adjacent code by their placement. Both C style comments and C++ single-line style comments are supported.
/** Doxygen single line comment */ /** * Doxygen comment * that extends over more than one line * this form should be used whenever there is more than one line */
/// Doxygen single line comment
Multiline comments composed of multiple C++ style comments are not preferred:
/// Doxygen comment /// that extends over more than one line
Doxygen comments are, by default, associated with the code that immediately follows the comment:
/// indicates whether or not the object currently allows any Foo actions. void isFooAllowed();
For some constructs it is more readable to place the comment after the code element it documents, which is accomplished by adding a '<' character after the Doxygen comment indicator. This may be used with either single or multi-line comments:
void doFooAt( int offset, ///< the offset into the Foo char* name /**< * the name to look up for this Foo action * in the FooMgr database. */ );
Placing the Doxygen comment after the element it documents in this way is preferred whenever the element is a member of a list, as in parameter declarations or enum values.
A class declaration must include a detailed description comment preceeding the class:
/** * FooFactory is a factory class that constructs instances of the * subclasses of Foo based on information obtained from the * foo-config file. */ class FooFactory {
The class comment is a good place to put general guidelines about how the methods in the class relate to one another.
By default, Doxygen groups the members within a class based on heuristics that use the public/protected/private scope and the method signatures. For simple classes this usually works well, and may be used. When a class has many methods, it is usually better to explicitly control how they are grouped in the documentation, and to provide additional documentation at the group level. To explicitly control grouping, add the 'nosubgrouping' Doxygen command to the class comment:
/**
* FooFactory is a factory class that constructs instances of the
* subclasses of Foo based on information obtained from the
* foo-config file.
*
* @nosubgrouping
*/
class FooFactory
{
Each group is then formed of the following elements:
For example (preceeded here by a non-Doxygen comment with a line of '=' characters so that it reads better in the source file):
// ================================================================ /** @name Searching * * The searching methods apply a compiled regular expression to a subject * string. All searching methods return a boolean result indicating whether * or not some match was found in the subject. To get information about * the match, use the Results methods. */ ///@{ ... the methods in the group ... ///@}
Each member function should have:
/// Search a string for matches to this regular expression. bool Search( const char * subject, ///< the string to be searched for a match int len = -1, ///< the length of the subject string int options = 0 ///< sum of any PCRE options flags ); /**< * Apply the regular expression to the subject string. * Optional parameter len can be used to pass the subject's length to * Search(). If not specified (or less than 0), strlen() is used * internally to determine the length. Parameter options can contain * any combination of options; for options documentation, see 'man pcre' * @returns true if a match is found. */
Including examples in the documentation is strongly encouraged. To embed an example, use the Doxygen "@code ... @endcode" construct:
/**< * May only be called after a successful search * and applies to the results of that call. * @returns true if there was an ith match, false if not * * Example:@code * RegEx matchBs("((B)B+)"); * UtlString getB; * UtlString getBs; * if (matchB.Search("xxaBBBBcyy")) * { * matchB.MatchString(&getBs,0); * matchB.MatchString(&getB,2); * } * @endcode * would set the UtlStrings * - getBs to "BBBB" * - getB to "B" */
Numbered and bulleted lists are supported in Doxygen comments using simple indentation conventions:
/**< * A numbered list is created using '#' characters: * # first numbered item * # second numbered item * # first item in nested numbered list (2.1) * * A bullet list uses '-' characters: * - first bullet * - second bullet * - nested bullet */
The following table summarizes the naming conventions:
Identifier | C | C++ | Java |
---|---|---|---|
package | n/a | shortname | |
class, union, struct | MeaningfulName | ||
exception class | n/a | MeaningfulException | |
interface | n/a | MeaningfulActionable | |
typedef | MeaningfulName, *MeaningfulNamePtr | n/a | |
enum | n/a | MeaningfulName | n/a |
function, method | PseudoClass_MeaningfulName | meaningfulName | |
accessor method | n/a | getX, setX | |
inquiry method | isX, hasX | ||
object, variable, argument | meaningfulName | ||
class attribute | n/a | mMeaningfulName | |
pointer variables | pMeaningfulName | n/a | |
reference variable | n/a | rMeaningfulName | n/a |
global variable | gMeaningfulName | n/a | |
static variable | sMeaningfulName | ||
#define, macro | MEANINGFUL_NAME | n/a | |
const, static final variable | MEANINGFUL_NAME | ||
source file | .c | .cpp | .java |
header file | .h | n/a |
Names should be readable and self-documenting. Abbreviations and contractions are discouraged. When confronted with a situation where you could use an all uppercase abbreviation instead use an initial upper case letter followed by all lower case letters. Shorter synonyms are allowed when they follow common usage within the domain.
All names should begin with a letter. Individual words in compound names are differentiated by capitalizing the first letter of each word as opposed to separating with an underscore. The use of special characters (anything other than letters and digits), including underscores is strongly discouraged. The first 31 characters of a name (including the prefix) must be unique, due to restrictions of various platforms. The uniqueness must not be due solely to a difference in case.
Prefixes are sometimes useful:
Suffixes are sometimes useful:
Filenames should only contain one period, to separate the file extension.
Function and method names should preferably begin with an action verb. Boolean-valued functions (those that have two possible return values) should use the "is" prefix as in "isEmpty()".
svn:ignore
A file that is generated in the course of building the software should be listed in its containing directory's svn:ignore
property. This prevents it from being displayed by 'svn status'. All files that 'svn status' reports as unknown (flags with '?') should be files that the user/developer created manually.
More information on svn:ignore
is in the Subversion book at http://svnbook.red-bean.com/en/1.1/ch07s02.html#svn-ch-7-sect-2.3.3
.
svn:mime-type
and svn:eol-style
In order to ensure that files checked out from the repository have the correct end-of-line characters for the platform onto which they are checked out, every file in the respository must have the appropriate values for the Subversion properties svn:mime-type
and/or svn:eol-style
.
All "binary" files in the repository should have a valid, non-text MIME media type specified for their svn:mime-type
property. (If no other type is suitable, "application/octet-stream" can be used.)
All text files must have a "text/*" svn:mime-type
property, and an svn:eol-style
property. Most text files are relevant to all platforms, and should have the value "native" for the svn:eol-style
property. This ensures that a Subversion checkout will use the right end-of-line characters for the host operating system.
Text files that are specifically for use only on MS-Windows and similar OS's should have an svn:eol-style
property of "CRLF", so that they can be checked out on other OS's and made visible to MS-Windows systems (through shared disks) without worrying about end-of-line translation.
Setting "automatic properties" in your Subversion configuration can automate setting these properties in most cases. A sample Subversion configuration file ($HOME/.subversion/config
or Application Data\Subversion\config
) that works for most of the file extensions used in sipX can be retrieved here. The list of officially-defined MIME media types is at http://www.iana.org/assignments/media-types
. SipX also uses WAV audio files, which have no officially-defined MIME media type, but do have a commonly understood extension type, audio/x-wav. More information on svn:mime-type
is at http://svnbook.red-bean.com/en/1.1/ch07s02.html#svn-ch-7-sect-2.3.2
. More information on svn:eol-style
is at http://svnbook.red-bean.com/en/1.1/ch07s02.html#svn-ch-7-sect-2.3.5
. More information on configuring Subversion is at http://scm.sipfoundry.org/svndoc/ch07.html#svn-ch-7-sect-1
.
There is a script in the developers' tool directory that will check that a working copy is following these rules. Commonly it is run from the top directory of a working copy via: sipXpbx/doc/developer/eol-check.sh sipX*
sf:tab
Most text files in sipX projects should not contain tab characters; they are allowed only when the file type specifically requires them and no alternative is available; the primary example of this is Makefiles.
Files which are exceptions to this rule can be marked by setting the sf:tab
Subversion property. The value 'initial' can be set for files for which tabs are allowed at the beginnings of lines. The principal use for this value is "makefiles". For this reason, sf:tab
= initial allows not only tabs at the beginnings of lines, but also tabs preceded by '#' (for commenting out lines).
The tab-check.sh script checks that all files with a text MIME-type do not contain tab characters.
The value sf:tab
= yes allows tabs in any location in a file. This value should only be used on files imported from external sources and files which require the use of tabs (e.g., some Windows build control files).
strcasecmp
and strncasecmp
The case-insensitive string comparison functions strcasecmp
and strncasecmp
were added to Posix relatively recently. Many older Windows development environments did not provide these functions, but rather provided functions named stricmp
and strnicmp
which provided the same functionality with the same interface. To avoid making each source file that needs these functions test its environment, the include file os/OsDefs.h
provides #define's to convert strcasecmp
and strncasecmp
into stricmp
and strnicmp
when necessary.
Any file which needs to use this functionality must use the names strcasecmp
and strncasecmp
and must #include os/OsDefs.h
to ensure they are mapped if the environment requires it. (Certain files obtained from third-party sources are exceptions to this rule.)
The primary purpose of style guidelines is to facilitate long-term maintenance. During maintenance, programmers who are usually not the original authors are responsible for understanding source code from a variety of applications. Having a common presentation format reduces confusion and speeds comprehension. Therefore, the following guidelines are specified based on the principles of good programming practice and readability. In the cases where two or more equally valid alternatives are available, one was selected to simplify specification. In the future, automated tools may be used to apply style guidelines to source code files.
Lines should not exceed 98 characters in order to display without wrapping on an 100-character display. If wrapping is required, try to break at an operator, and start the next line with the operator vertically aligned. For example:
cout << "This is an example of a line which must be wrapped, value = " << value << endl;
Each statement should begin on a new line.
In all text files, the final line should end with the appropriate end-of-line characters (for the host operating system). (Zero-length text files are acceptable, as they contain no lines.)
For comments meant to be extracted by an automated documentation tool, follow the Java convention of using the standard C comment delimiters with an extra asterisk on the first one, as shown:
/** * This is a module, class, function, or instance variable comment * that will be extracted by an automated documentation tool. */
This will provide a consistent look across all source code files, and should facilitate creation of automated documentation tools.
Such comments should be used to describe classes, methods, and global or instance variables.
Code block comments should precede the block, be at the same indentation level, and be separated by a blank line above and below the comment. Brief comments regarding individual statements may appear at the end of the same line, and should be vertically aligned with other comments in the vicinity for readability.
Use a single blank line to separate logical groups of code to improve readability. In source files, use two blank lines to separate each function.
Spacing around operators and delimiters should be consistent. In general, insert one space before or after each operator to improve readability.
if (value == 0){ // right
if ( value == 0 ) { // not recommended
void doIt(int v); // right
void doIt( int v ); // not recommended
value = object->GetValue(); // right
value=object -> GetValue(); // wrong
The contents of all code blocks should be indented to improve readability. Indent using 3 (recommended), 4, or 8 spaces for each level. Do not use tabs, use spaces. Most editors can substitute spaces for tabs. See also the sf:tab
Subversion property.
Of the three major brace styles, two are acceptable - with the first one listed being preferable. For existing files, be consistent with the brace style already in use in the file - Do no mix styles within a file.
if (condition) if (condition) while (condition) int main() { { { { . . . . . . . . . . . . } } } } else if (condition) { . . . } else { . . . } struct MyStruct { int x; int y; }
if (condition) { if (condition) { while (condition) { int main() . . . . . . . . . { } } } . . . else if (condition) { } . . . } else { . . . } struct MyStruct { int x; int y; }
char* text; // right
char *text; // not recommended
char* doSomething(int* x); // right
char *doSomething(int *x); // not recommended
All control statements should be followed by an indented code block enclosed with braces, even if it only contains one statement. This makes the code consistent and allows the block to be easily expanded in the future. For example:
if (value == 0) // right { doSomething(); }
if (value == 0) doSomething(); // wrong - no block, not indented if (value == 0) doSomething(); // wrong - no block
Conditional statements found in if, while, and do statements should be explicit based on the data type of the variable being tested. For example:
(C,C++)
int value = getValue(); if (value == 0) // right { doSomething(); }
if (!value) // wrong - not explicit test { doSomethingElse(); }
bool value = getValue(); // could be RWBoolean too. if (!value) // right { doSomethingElse(); }
(Java)
boolean value = getValue(); if (!value) { // right - this is explicit doSomethingElse(); // test for boolean type }
#include <stdlib.h> // right #include <stdio.h> // #include <Xm/Xm.h> // #include "meaningfulname.h" //
#include "/proj/util/MeaningfulName.h" // wrong - explicit path, #include <stdlib.h> // out of order, #include </usr/include/stdio.h> // path for system file, #include "Xm/Xm.h" // local include of library file
Each variable should be individually declared on a separate line. Variables may be grouped by type, with groups separated by a blank line. Variable names should be aligned vertically for readability. There is no required ordering of types, however some platforms will give optimal performance if declarations are ordered from largest to smallest (e.g., double, int, short, char).
int* a; // right int b; // int c; // double d; // double e; // double a; // right int b; // double d; // acceptable - not grouped by type int b; // int* a; // double e; // int c; //
int* a, b, c; // wrong - not individually declared, not // on separate lines int* a, // wrong - not individually declared b, // c; //
The two preceding examples are prone to error; notice that a is declared as a pointer to integer and b and c are declared as integers, not as pointers to integers.
#ifdef MeaningfulNameInit // the flag is called MeaningfulNameInit #define EXTERN // create the variable (only in main.cpp) #else #define EXTERN extern // just a reference (default) #endif EXTERN ErrorLogger errorLog; #undef EXTERN
#include "meaningfulname.h"
#define MeaningfulNameInit #include "meaningfulmame.h" #undef MeaningfulNameInit
Use only the uppercase suffixes (e.g., L, X, U, E, F) when defining numeric constants. For example:
const int value = A73B2X; // right, hexadecimal constant const double evalue = 1.2E9; // right, scientific notation constant
const float fvalue = 1.2e9; // wrong, lowercase e
enum CompassPoints { // Enums used to specify direction. North = 0, // South = 1, // East = 2, // West = 3 // };
enum States { STATE_ERR, STATE_OPEN, STATE_RUNNING, STATE_DYING };
struct MeaningfulName // This is a struct of some data. { int firstInteger; // This is the first int. int secondInteger; // This is the second int. double firstDouble; // This is the first double. double secondDouble; // This is the second double. };
class Value : public BaseValue { public: Value(); //: Default constructor. Value(const Value& oldValue); //: Copy constructor. ~Value(); //: Destructor. void setValue(int newValue); //: Set value. int getValue(); //: Get value. protected: void incrementValue(); //: Increment value. private: int value; //: The value. };
Local variables can be declared at the start of the function, at the start of a conditional block, or at the point of first use. However, declaring within a conditional block or at the point of first use may yield a performance advantage, since memory allocation, constructors, or class loading will not be performed at all if those statements are not reached.
Specify a break statement after every case block, including the last one unless multiple labels are used for one selection of code. It is recommended that a default case always be defined.
Where practical, have only one return from a function or method as the last statement. Otherwise, minimize the number of returns. Possibly highlight returns with comments and/or blank lines to keep them from being lost in other code. Multiple returns are generally not needed except for reducing complexity for error conditions or other exceptional conditions.
Avoid the use of casts except where unavoidable, since this can introduce run-time bugs by defeating compiler type-checking. Working with third-party libraries (e.g., X or Motif) often requires the use of casts. When you need to cast, document the reasons.
Use constants instead of literal values wherever possible. For example:
const double PI = 3.141259; // right const char APP_NAME = "ACME Spreadsheet 1.0"; // right
area = 3.141259 * radius * radius; // not recommended cout << "ACME Spreadsheet 1.0" << endl; // not recommended
In general, explicitly initialize all variables before use.
It is very strongly recommended that you initialize all pointers either to 0 or to an object. Do not allow a pointer to have garbage in it or an address in it, that will no longer be used.
Include the system error text for every system error message.
(C, C++) Check every call to malloc or realloc unless you know your versions of these calls do the right thing.
Methods should not be longer than a single page of code.
Always document a null body for a while statement so that it is clear that the null body is intentional and not missing code.
Consider your comments a story describing the system. Expect your comments to be extracted by a robot and formed into a man page. Class comments are one part of the story, method signature comments are another part of the story, method arguments another part, and method implementation yet another part. All these parts should weave together and inform someone else at another point of time just exactly what you did and why.
Comments should document decisions. At every point where you had a choice of what to do place a comment describing which choice you made and why. Archaeologists will find this the most useful information.
Explicitly comment variables changed out of the normal control flow or other code likely to break during maintenance. Embedded keywords are used to point out issues and potential problems. Consider a robot will parse your comments looking for keywords, stripping them out, and making a report so people can make a special effort where needed.
Gotcha Keywords
// :TODO: tmh 960810: possible performance problem // We should really use a hash table here but for now we'll // use a linear search. // :KLUDGE: tmh 960810: possible unsafe type cast // We need a cast here to recover the derived type. It should // probably user a virtual method or template.
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#ifdef DEBUG cout << "MeaningfulName::doSomething: about to do something" << endl; #endif
void A::function(int notChanged); // default: pass by value void B::function(const C& bigReadOnlyObject) // pass by const reference void C::function(int notChanged, int& result); // pass by reference
extern "C" { void aFunction(); // single non-C++ function prototype } extern "C" { #include "functions.h" // library of non-C++ functions }
For additional background and suggestions, there are a number of coding standard documents available on the Web; these are included for reference only and are not a part of this standard: