CORRECT VERSION NOTICE |
This document is subject to frequent detailed changes critical to correct engineering. Any hard copy may be out of date. Readers are responsible to ensure they are reading the most current document. Confirm the correct version by checking the on-line version of the document, the on-line master list or the project authority. |
Table of Contents
1. Introduction
This document describes the coding guidelines for the various code files that
are written by the Software Engineering Team at Zeriph. The goal of these guidelines
is to increase portability, reduce maintenance, and above all improve clarity. Some
of the content as well as the coding style presented here is a mixed derivative of
styles presented in the References section below.
The secondary purpose of this document is to describe the layout of the code throughout the Omni library in the event one wishes to fix, add, or create a derivative work from the full source.
2. General RulesThe secondary purpose of this document is to describe the layout of the code throughout the Omni library in the event one wishes to fix, add, or create a derivative work from the full source.
\n
instead of \r\n
or \r
).some_file.cpp
).
Any coding styles not mentioned in this file should be brought up to the team to ensure the style meets any
specifications that might be required. NOTE: this only applies to those working internally on the library and
does not need to apply to a general use case of this library.
In general the code presented for any examples will try to be as technically correct as possible, however it is not the technicalities of the actual code, but the idea presented (syntax layout/etc.) that should be paid attention to throughout this style document.
4. General LayoutIn general the code presented for any examples will try to be as technically correct as possible, however it is not the technicalities of the actual code, but the idea presented (syntax layout/etc.) that should be paid attention to throughout this style document.
4a. Source File Layout
Program/implementation (.c/.cpp/etc.) files should be organized in the following manner:
See Naming for more information on how to name items within your source file.
4b. Header File Layout- A comment with the copyright notice (unless it is 3rd party code that does not allow copyright). Be sure to include any other copyright notices that may be required (i.e. the GPL if using code that comes direct from GPL copyrighted code).
- The
omni/global.hpp
include if necessary. - Library imported functionality (e.g.
#include <omni/...>
). - System imported functionality (e.g.
#include <map>
). #define
statements.- File local members and functions.
- File local externally linked function or member declarations.
- Class member variable or property instantiations and any forward declarations that may be needed.
- Static or externally linked class member variables or function implementations.
- Public class function implementations.
- Protected class functions implementations.
- Private class function implementations.
- If implementing code from a header file, follow the layout of the header file. For instance, if a class is defined with 4 functions, those 4 functions will be implemented in the same order in the program file.
- Only function implementation should be in the program files itself, everything else (interface declarations,
enum
types, global/local variables,class
orstruct
definitions, etc.) will be in the header file. - It is OK to put forward declarations in program files that might be needed within the file.
- If any member functions are defined within a namespace only, they will be treated as a
static member
of a class and will be put in theglobal/static member
area of the file. - Any implementation in a header file (such as the case with templates and inline functions) will adhere to the standards put forth in this document.
See Naming for more information on how to name items within your source file.
Interface files (.h/.hpp/etc.) should be organized in the following manner:
4c. Namespace Layout- A comment with the copyright notice (unless it is 3rd party code that does not allow copyright). Be sure to include any other copyright notices that may be required (i.e. the GPL if using code that comes direct from GPL copyrighted code).
- A brief comment describing the purpose of the file if it is for something other than class or function definition. The comment should not contain revision information or revision control tags.
- The single inclusion define (e.g.
#if !defined(OMNI_HEADER_HPP)
) that prevents accidental double inclusion. - Local
#include
's, if needed (e.g.#include <omni/...>
). - System
#include
's, if needed (e.g.#include <map>
). - Macro's and other
#define
constants. - Opening brace of C++ guard (
#ifdef __cplusplus
), if needed. - Global variable declarations (using
extern
where needed). - Global function declarations (using
extern
where needed). - Namespaces, classes and data structures.
- Closing brace of C++ guard if opening brace exists.
- The complimenting
#endif
for preventing accidental double inclusion.
- Try and keep any actual implementation details (code) out of the header file, unless it cannot be avoided (as with templated types).
- Try and keep the definitions in alphabetical order unless it cannot be avoided; for example, a static templated function named
foo
might use a static templated function namedmore_foo
, somore_foo
must be defined and declared beforefoo
. - Alphabetical ordering can be ignored if using a type within a declaration that uses another type below it and a forward declaration is not possible or would convolute the code (as can be the case with templated types).
- There may be instances where it is unavoidable to put implementation (code) in the header file (for instance, when making use of templates); this is OK, but try and keep as much code as possible in the actual implementation file (the .c or .cpp file) for cleaner separation of implementation, declaration and problem domains.
Namespaces within the library should be organized in the following manner:
See Naming for more information on how to name items within a namespace.
4d. Class Layouttypedef
declarations that do not require forward declarations.- Constants.
- Child namespaces.
- Inner classes/namespaces (ad infinitum).
- Inner functions (ad infinitum).
- Classes and structures, to include enum class types.
- Namespace functions.
typedef
declarations that utilizes classes within the namespace.
typedef
declartions, functions and classes can be placed where they need to be for the code to build, but should
be grouped in the code as closely to these standards as possible.See Naming for more information on how to name items within a namespace.
Classes and structures within the library should be organized in the following manner:
All functions should try and be arranged in alphabetical order when feasible.
Any member properties/variables should also be arranged alphabetical unless doing so will cause additional padding. In this case, the member variables should be ordered in such a way as to reduce the padding as much as possible, and initialized in the constructor member initialization list appropriately.
Note that these standards do not strictly need to be adhered to in the case that types or functions need to be used within a later declared/defined type or function. In these instances,
See Naming for more information on how to name items within the class.
5. Formattingtypedef
declarations- Rule of 3/5 constructors.
- Additional class constructors.
- The class destructor.
- Public members.
- Public functions.
- Public operators.
- The OMNI_MEMBERS_FW macro if applicable (should be in most classes).
- The OMNI_OSTREAM_FW macro if applicable (should be in most classes).
- Public friend operators.
- Public static members.
- Public static functions.
- Protected members.
- Protected functions.
- Protected operators.
- Protected friend operators.
- Protected static members.
- Protected static functions.
- Private members.
- Private functions.
- Private operators.
- Private friend operators.
- Private static members.
- Private static functions.
All functions should try and be arranged in alphabetical order when feasible.
Any member properties/variables should also be arranged alphabetical unless doing so will cause additional padding. In this case, the member variables should be ordered in such a way as to reduce the padding as much as possible, and initialized in the constructor member initialization list appropriately.
Note that these standards do not strictly need to be adhered to in the case that types or functions need to be used within a later declared/defined type or function. In these instances,
typedef
declartions, functions and classes can be placed where they need to be for the code to build, but should
be grouped in the code as closely to these standards as possible.See Naming for more information on how to name items within the class.
5a. Indentations
Indentions will be 4 spaces each and follow a tree structure. In other words,
following a definition of some type, if there is code that supports the definition
(for instance as in an
Example:
5b. Bracesif
statement, there is code that follows the
if
) this code shall be 1 indent and any subsequent areas shall be indent count +1.Example:
if (...) { // 0 indent // 1 indent if (x == y) { // 2 indents } else { if (z > 0) { // 3 indents } } }Other notations on indentions in regards to placement of braces or function names, etc. are noted in the following sub paragraphs.
Though there is no technical reason for a different placement and position of braces, it is preferred
(for readability) that the opening brace be put on the same line at the end, and the closing brace as
the first and only (in most cases) character on a line.
Example:
Example:
5c. ParenthesisExample:
if (...) { // do x,y,z } // single lines are acceptable if doing small computations: if (...) { /* do x */ }This guideline does not apply to functions. Functions shall have the opening brace at the beginning of a new line immediately following the function definition.
Example:
int function_name(int x) { // function body return value; }Other notations on brace placement can be found in the below sections.
It is generally a good idea to use parenthesis in expressions involving mixed operators.
If there is an area of code that will have multiple parameters in between the parenthesis, you should treat the parenthesis as they were braces of an
Example:
5d. SpacesIf there is an area of code that will have multiple parameters in between the parenthesis, you should treat the parenthesis as they were braces of an
if
statement in regards
to placement.Example:
// mixed operators int i = (((a * b) / (c * d)) * 10) + 1; // expansive parenthesis object* obj = new object( param1, // comment on param1 param2, // other comment param3, param4, param5, param6 );
The use of spaces depends (mostly) on function-versus-keyword usage. Use a space after (most) keywords.
The notable exceptions are
Example:
There should be 1 space immediately following the last closing code (the last parenthesis before the opening brace in the above
There will be no spacing immediately following an opening parenthesis or immediately before a closing parenthesis.
Example:
If declaring a pointer type, the pointer operator (the asterisks
Example:
Use 1 space on either side of a most binary/ternary operators (
Example:
There will be no space after unary operators (
Example:
No space before or after the increment and decrement unary operators,
Example:
No space before or after the scope resolution operators (
Example:
Any initialization lists will be separated by a space before and after the colon (
Example:
An initialization list can be on a single line, or multiple lines if there are multiple entries. Be sure to line up the entries accordingly.
Example:
5e. Declarationssizeof
, typeof
, exit
and any other reserved
keywords that can look/act somewhat like a function.Example:
if (x == 1) { // space following control statement 'if' if (y > sizeof(object)) { // no space after 'sizeof' exit(1); // no space after 'exit' } }
There should be 1 space immediately following the last closing code (the last parenthesis before the opening brace in the above
if
statements for instance).There will be no spacing immediately following an opening parenthesis or immediately before a closing parenthesis.
Example:
s = sizeof( object ); // WRONG s = sizeof(object); // RIGHT
If declaring a pointer type, the pointer operator (the asterisks
*
) will be immediately before the data type.Example:
char * value; // WRONG char *value; // WRONG char* value; // RIGHT
Use 1 space on either side of a most binary/ternary operators (
<
, =
, +
, -
, *
, etc.).Example:
a = (((1+1)*2)/4); // WRONG a = (((1 + 1) * 2) / 4); // RIGHT a+=5; // WRONG a += 5; // RIGHT
There will be no space after unary operators (
&
, *
, !
, ~
, sizeof
, etc.).Example:
char* a = & b; // WRONG char* a = &b; // RIGHT int x = ~ (y + 1); // WRONG int x = ~(y + 1); // RIGHT
No space before or after the increment and decrement unary operators,
++
, --
.Example:
x ++; // WRONG x++; // RIGHT
No space before or after the scope resolution operators (
.
, ->
, ::
).Example:
x . get_ref() -> base_class :: some_func(); // WRONG x.get_ref()->base_class::some_func(); // RIGHT
Any initialization lists will be separated by a space before and after the colon (
:
) and after each initializer.Example:
class object { public: object(const char* name, int count) : m_name(name), m_count(count) {} private: const char* m_name; int m_count; };
An initialization list can be on a single line, or multiple lines if there are multiple entries. Be sure to line up the entries accordingly.
Example:
object(const char* name, int count) : m_name(name), m_count(count) {} // Braces can be here if nothing is to be done // or here on next lineLastly, do not leave trailing whitespace at the ends of lines.
One declaration per line is recommended since it encourages commenting, though it is acceptable to
put all declarations on a single line (especially when multiple are needed).
Example:
When declaring multiple variables of the same base type, do not mix the variable types (reference, pointer, etc.) on the same line.
Example:
Another alternative is to line up a group of declarations (with spaces, not tabs), while acceptable, it is preferred to use the above method mentioned.
Example:
Function level declarations will be only at the beginning of blocks of code (a block is anything surrounded by braces
Example:
Avoid local declarations that hide higher level declarations at a function level, in other words do not declare the same variable name in an inner block, while syntactically correct, it can lead to confusion in code.
Example:
Function variables should be initialized, when feasible, when they are declared.
Example:
5f. StatementsExample:
int level = 0; // comment on level int size = 100; // comment on size int level = 0, size = 100, tmp, x, y, z; // OK, just be sure to comment
When declaring multiple variables of the same base type, do not mix the variable types (reference, pointer, etc.) on the same line.
Example:
int x = 0, y[SIZE] = { 0 }; // WRONG int x = 0; // RIGHT int y[SIZE] = { 0 }; // RIGHT
Another alternative is to line up a group of declarations (with spaces, not tabs), while acceptable, it is preferred to use the above method mentioned.
Example:
int level = 0; int size = 0; float current_data = 0.0;
Function level declarations will be only at the beginning of blocks of code (a block is anything surrounded by braces
{
and }
). This can be ignored if initializing an object or type that will
consume resources and only needs to be declared if something is to happen.Example:
void set_state(int x, int y) { // initialize variables here std::vectorgroup; object tmp(); int counter = 0; if (x == 0 && y == 0) { // alert or something else } else { // instantiate memory/CPU intensive object/resource resource* resrc = new resource(); // do something with resource delete resrc; } }
Avoid local declarations that hide higher level declarations at a function level, in other words do not declare the same variable name in an inner block, while syntactically correct, it can lead to confusion in code.
Example:
void set_state(int x, int y) { int counter = 0; // other code that uses the 'counter' variable for (int counter = 0; counter < 10; ++counter) { // this could lead to errors in this section of code // remedy is to rename this 'inner' variable to something else } }
Function variables should be initialized, when feasible, when they are declared.
Example:
std::listgroup; // no need to 'initialize' to anything int x = 0; // initialize x
to zero object* obj = NULL; // pointer types can be NULL if allowable by the type object* obj = new object(); // initializing tmp to a new instance
In general, each line should contain at most one statement. If putting multiple statements on a single line,
try and group them such that they preform similar functionality. The
Example:
Do not nest the ternary conditional operator (
Example:
Functions that do not return a value (
Example:
If returning a value, the return statement should not enclose its return value in parenthesis, unless it is a compound return statement.
Example:
5f1. Compound statementsbreak
statement is OK to put
on the same line as another statement as long as it is easy to read.Example:
++i; ++x; // OK ++i; break; // OK some_func(); break; // OK do_something_with_var(x); ++x; ++i; // NOT OK
Do not nest the ternary conditional operator (
... ? ... : ...
).Example:
int num = ((count < x) ? ((x > 10) ? x : 0) : 1); // WRONG int num = ((count < x) ? 10 : x); // RIGHT
Functions that do not return a value (
void
) should not have a return
statement as the
last statement in the function.Example:
void set_state(int x) { // do something return; // WRONG }
If returning a value, the return statement should not enclose its return value in parenthesis, unless it is a compound return statement.
Example:
return (val); // WRONG return val; // RIGHT return (val + 10 - some_func()); // OK
Compound statements are statements that contain lists of statements enclosed in braces.
The enclosed list should be indented one more level than the compound statement itself.
The opening left brace should be at the end of the line beginning the compound statement
and the closing right brace should be alone on a line, positioned under the beginning of
the compound statement (see examples below). Note that the left brace that begins a function
body is the only occurrence of a left brace which should be alone on a line.
Braces are also used around a single statement when it is part of a control structure, such as an
Example:
All statements and control blocks that allow for optional braces are to include braces. This is for clarity of code.
Example:
Any empty statements can have the opening and closing braces immediately following it.
Example:
5g. Scope references and the 'Braces are also used around a single statement when it is part of a control structure, such as an
if-else
or for
statement.Example:
if (condition) { if (other_condition) { statement; } }
All statements and control blocks that allow for optional braces are to include braces. This is for clarity of code.
Example:
/* The following is valid, but could potentially be confusing to read depending on context of the code */ if (condition) do_something(); // This is preferred if (condition) { do_something(); }
Any empty statements can have the opening and closing braces immediately following it.
Example:
for (;;) { }
this
' operator5g1. Scope
When referencing a variable or function, the scope of that variable must be included as well. This is to ensure the
code is immediately readable and understandable by anyone not familiar with the structure of the code.
Take the following code as an example:
When looking at the above code, it is not immediately clear as to where the function
Take the following code as an example:
To this affect all variables and functions will have at least 1 level of scope applied for reference. To illustrate this point, take the above code as an example applied with the appropriate scope:
By explicitly defining the scope of the variable or function, you not only make the code more readable, but also remove any possibilities of inadvertently calling the wrong function or having the compiler assume which function you meant to call; while it might lead to more verbosity throughout the library's internal code, it removes ambiguity which leads to cleaner, less buggy code.
Higher levels of scope may be applied where more clarity is needed, but typically 1 level of scope is sufficient. As an example, the following C# code illustrates how adding higher levels of scope can add clarity; this example uses part of the .NET Framework in C# to illustrate this point due to the lack of examples across the library and C++ standards:
If the single level of scope is the current class, then the
5g2. The 'Take the following code as an example:
void some_function(int newidx) { // ... other code do_something_with_updated_index(newidx); }
When looking at the above code, it is not immediately clear as to where the function
do_something_with_updated_index
resides, in other words, the scope of the function is unclear and could potentially lead to confusion by other developers.
It can also lead to naming conflicts.Take the following code as an example:
class base_class { public: static bool some_function(double val) { return (val > 0.0d); } }; class some_class : base_class { public: bool some_function(long val) { return (val > 100L); } void do_something() { // some other code some_function(get_input()); // which one are we intending to call? } };
To this affect all variables and functions will have at least 1 level of scope applied for reference. To illustrate this point, take the above code as an example applied with the appropriate scope:
class base_class { public: static bool some_function(double val) { return (val > 0.0d); } }; class some_class : base_class { public: bool some_function(long val) { return (val > 100L); } void do_something() { // some other code this->some_function(get_input()); base_class::some_function(static_cast(get_input())); } };
By explicitly defining the scope of the variable or function, you not only make the code more readable, but also remove any possibilities of inadvertently calling the wrong function or having the compiler assume which function you meant to call; while it might lead to more verbosity throughout the library's internal code, it removes ambiguity which leads to cleaner, less buggy code.
Higher levels of scope may be applied where more clarity is needed, but typically 1 level of scope is sufficient. As an example, the following C# code illustrates how adding higher levels of scope can add clarity; this example uses part of the .NET Framework in C# to illustrate this point due to the lack of examples across the library and C++ standards:
Thread.Sleep(1000); Thread.AddUserRequest(someObj);For the first two functions above, their first level up is the
Thread
class, however it is not immediately clear
if Thread
is the same class for each function, or if it is the same name within two separate namespaces.
For this you can use 1 more level up in scope, or you can elect to use the full scope, for example:
System.Threading.Thread.Sleep(1000); CustomFramework.Utils.Threading.Thread.AddUserRequest(someObj);
If the single level of scope is the current class, then the
this
operator will be used for clarity.
this
' operator
The
The
For clarity the
If the variable or function you are referencing within a child class is from its parent class, it is still preferred to reference it via the
5h. Examplesthis
operator is a special operator that returns a reference to the current instance of the
object you are operating in (the class you are in). If no this
operator is explicitly applied to the
variable or function, one is implicitly applied by the compiler (scope permitting).The
this
operator has 0 impact on performance since it is simply referring to a scope, not an operation.For clarity the
this
operator will be put on all member variables and functions for immediate understanding
of what scope the variable or method is a part of.If the variable or function you are referencing within a child class is from its parent class, it is still preferred to reference it via the
this
operator. It is ok, in certain instances, to reference parent members and functions
via the parent class; examples would be specific language rules requiring the use of the parent keyword or in an event
when using the parent class would add clarity to code.
if, if-else, if-else-if statements:
Note that the right brace before the else and the right brace before the
for statements:
When using the comma operator in the initialization or update clauses of a
The infinite loop is written using a
while statements:
do-while statements:
switch statements:
The last
All switch statements should include a default case. Don't assume that the list of cases covers all possible cases.
New, unanticipated, cases may be added later, or bugs elsewhere in the program may cause variables to take on unexpected values.
Notes on
Example:
6. Hierarchical Typesif (condition) { statements; } if (condition) { statements; } else { statements; } if (condition) { statements; } else if (condition) { statements; }
Note that the right brace before the else and the right brace before the
while
of a do-while
statement (see below) are the only places where a right brace appears that is not alone on a line.for statements:
for (initialization; condition; update) { statements; }
When using the comma operator in the initialization or update clauses of a
for
statement, it is suggested that no
more than three variables should be updated. More than this tends to make the expression too complex or confusing.
In this case it is generally better to use separate statements outside the for
loop (for the initialization clause),
or at the end of the loop (for the update clause).The infinite loop is written using a
while
or for
loop. While infinite loop's are useful tools
it's best to be sure there is some rudimentary mechanism to allow the loop to exit (on user input for instance, or the
program shutting down).while (!exit_condition) { statements; if (exit_condition) { break; } statements; } for ( ;!exit_condition; ) { statements; if (exit_condition) { break; } statements; }
while statements:
while (condition) { statements; }
do-while statements:
do { statements; } while (condition);
switch statements:
switch (condition) { case ABC: case DEF: statements; break; case XYZ: statements; break; default: statements; break; }
The last
break
is, strictly speaking, redundant, but it is recommended form nonetheless because it prevents
a fall-through error if another case
is added later after the last one. In general, the fall-through feature
of the switch
statement should rarely, if ever, be used (except for multiple case labels as shown in the example).
If it is, it should be commented for future maintenance.All switch statements should include a default case. Don't assume that the list of cases covers all possible cases.
New, unanticipated, cases may be added later, or bugs elsewhere in the program may cause variables to take on unexpected values.
Notes on
switch
statements:
- The
case
statement should be indented one level more than theswitch
statement. - The
case
statement should be on a line separate from the statements within the case. - The
break
statement should be indented to the same level as the code within the case.
switch
whenever the blocks of statements contain more
than a couple of lines.Example:
switch (condition) { case ABC: case DEF: statement1; . . statement2; break; case XYZ: statement1; . . statement2; break; default: statements; break; }
Hierarchical types (
Inner types should be specific in nature to its parent type.
Avoid too many inner types or too deep a tree of inner types. The tree depth excludes namespaces.
Generic types should still be contained within a
See Naming for more information on how to name hierarchical types.
7. Functionsclass
, struct
, enum
, etc.) shall be laid out in such a
manner that best suits the design needs of that specific type. This document will not cover design specifications,
only general guidelines to certain idioms.Inner types should be specific in nature to its parent type.
Avoid too many inner types or too deep a tree of inner types. The tree depth excludes namespaces.
Generic types should still be contained within a
namespace
, static/abstract class
, or some
other container mechanism. This is for clarity of code and to split out any generic code that could potentially be
reused by other code.See Naming for more information on how to name hierarchical types.
Treat reserved keywords that can act like functions as such (except for the exit and return statements).
Example:
In program files, functions should be separated by one blank line.
Example:
When declaring function prototypes with parameters (which should be in the header file), include the parameter name as well as its type.
Example:
It is not required to give a parameter name in C/C++ when defining the method; however, it does give more clarity as to what the parameter should potentially do. This is especially helpful when only looking at the header file, or when using an IDE that has some sort of 'IntelliSense' engine.
That being said; functions should be simple, as short as possible, do one thing and do it well. It is OK to have longer line count functions if it cannot be avoided, for instance, a conceptually simple function that consists of a really long, but simple, case statement is OK to be long, however, an overly complex function that is only 3 lines (for example) might need to be rewritten for clarities sake (unless there is a reason for the complexity, like speed or efficiency for instance).
Try to limit the number of local variables used within a function to in between 5 and 10. Anything more could add to the complexity of the code and thus be more error prone or not as easy to understand; this also helps your stack be generally smaller and thus more efficient.
An idea that is to be followed with functions:
If the function looks like it might not be easily understood by a high school freshman with a good beginners programming book, chances are you might not understand either in a couple of weeks.
To this affect, in any function you write, be sure it can be understood; either through the syntax and semantics of the code itself or through commenting, preferably through commenting.
7a. Use of Example:
sizeof x // Valid, but not preferred method sizeof(x) // Preferred
In program files, functions should be separated by one blank line.
Example:
int get_value() { return value; } void set_value(int val) { value = val; }
When declaring function prototypes with parameters (which should be in the header file), include the parameter name as well as its type.
Example:
int get_value(); // no parameters void set_value(int new_value); // parameter is 'new_value' and is of type 'int'
It is not required to give a parameter name in C/C++ when defining the method; however, it does give more clarity as to what the parameter should potentially do. This is especially helpful when only looking at the header file, or when using an IDE that has some sort of 'IntelliSense' engine.
That being said; functions should be simple, as short as possible, do one thing and do it well. It is OK to have longer line count functions if it cannot be avoided, for instance, a conceptually simple function that consists of a really long, but simple, case statement is OK to be long, however, an overly complex function that is only 3 lines (for example) might need to be rewritten for clarities sake (unless there is a reason for the complexity, like speed or efficiency for instance).
Try to limit the number of local variables used within a function to in between 5 and 10. Anything more could add to the complexity of the code and thus be more error prone or not as easy to understand; this also helps your stack be generally smaller and thus more efficient.
An idea that is to be followed with functions:
If the function looks like it might not be easily understood by a high school freshman with a good beginners programming book, chances are you might not understand either in a couple of weeks.
To this affect, in any function you write, be sure it can be understood; either through the syntax and semantics of the code itself or through commenting, preferably through commenting.
goto()
The
Since the
Again, while the
8. Commentsgoto
statement should be used extremely sparingly. While the goto
statement is a completely valid
tool, it can be error prone if not used correctly (similar to a case
statement without a break
). Any
code that uses a goto
can be restructured to not use the goto
, typically with the addition
of an if
or some other control flow statement.Since the
goto
statement is syntactically equivalent to a jmp
statement in assembler, a
goto
can be valuable in instances where memory and CPU cycles are in a very limited supply. Even in these
cases, boolean values in conjunction with proper if
statements could avoid the goto
with no
added overhead.Again, while the
goto
statement is a valid and potentially useful tool, it is best to avoid them and use
well-structured code instead.
Commenting is vitally important to quickly and easily understanding a programs control structure and flow. It is because
of this that commenting can be good and bad.
To avoid excess commenting, it is typically best to leave comments out of the function bodies themselves. It is OK to comment areas of a function to warn about something in the code or to better explain why something is the way it is (as per the paragraph below). It is easier, however, to read the code if the function itself is commented (see the section below).
Typically you do not want to try and explain how your code works, more so, what the code is supposed to do and potentially why you did something the way you did (if it's not overtly clear by the code).
Commenting should be limited in scope to the particular area you are commenting on, it should not include author names, bug tracking information, or other items that do not directly reflect or refer to that specific area of code. In other words, a comment should reflect and clarify the code it is being written for.
Both C89
There are three styles of comments: block (multi-line), single-line, and trailing.
For single-line comments, the C89 or C99 style is OK to use. Both styles are also OK for a trailing comment, if the trailing comment fits on the same line. However, it should be noted that the C99 style is preferred.
For block (multi-line) comments, if the comment is more than 2 lines, do no use the C99 style commenting, only use C89 style. This is true for trailing comments as well.
C89 style block comments should be in the form of having the opening
Example:
One-line comments alone on a line should be indented to that of the code that follows.
Example:
Trailing comments should be short. They can be tabbed uniformly or left at your discretion. They must be at least 1 space from the code. They can also be either C89 or C99 style commenting.
Example:
Example:
This does not preclude the use of other commenting types, it is OK to mix and match commenting types within a file.
Example:
It is also important to comment your data (variables) when it is not obvious as to what they do or in the event you are declaring a global variable or defining a member variable within a class.
Lastly, always remember, you never know who will be looking at the comments, so try to avoid any asinine comments or comments that are insulting or degrading.
8a. Function CommentsTo avoid excess commenting, it is typically best to leave comments out of the function bodies themselves. It is OK to comment areas of a function to warn about something in the code or to better explain why something is the way it is (as per the paragraph below). It is easier, however, to read the code if the function itself is commented (see the section below).
Typically you do not want to try and explain how your code works, more so, what the code is supposed to do and potentially why you did something the way you did (if it's not overtly clear by the code).
Commenting should be limited in scope to the particular area you are commenting on, it should not include author names, bug tracking information, or other items that do not directly reflect or refer to that specific area of code. In other words, a comment should reflect and clarify the code it is being written for.
Both C89
/*...*/
and C99 //...
style commenting is acceptable.There are three styles of comments: block (multi-line), single-line, and trailing.
For single-line comments, the C89 or C99 style is OK to use. Both styles are also OK for a trailing comment, if the trailing comment fits on the same line. However, it should be noted that the C99 style is preferred.
For block (multi-line) comments, if the comment is more than 2 lines, do no use the C99 style commenting, only use C89 style. This is true for trailing comments as well.
C89 style block comments should be in the form of having the opening
/*
followed by a new line, then either
a single space followed by *
, or 4 spaces, followed by the comment text, and the closing */
should be on a new line and should align with the previous lines *
if asterisks are used, otherwise, it
should be the only text on the line. Note: the "single space" is meant to indicate the indentation, so
if the comment is double tabbed over (e.g. 8 spaces), the "single space" would be the 9th space.Example:
123 <- column /* * Here is a block comment. * The comment text should be tabbed or spaced over uniformly. * The opening slash-star and closing star-slash are alone on a line. * The closing star-slash is aligned with the previous lines star. */Or the following:
1234 <- column /* Here is a block comment. The comment text should be tabbed or spaced over uniformly. The opening slash-star and closing star-slash are alone on a line. The closing star-slash is not tabbed or spaced over. */
One-line comments alone on a line should be indented to that of the code that follows.
Example:
if (argc < 3) { // Argument count is not the number we need, show the usage show_usage(); exit(1); }
Trailing comments should be short. They can be tabbed uniformly or left at your discretion. They must be at least 1 space from the code. They can also be either C89 or C99 style commenting.
Example:
if (a == EXCEPTION) { b = true; /* C89 comment */ } else { b = isprime(a); // C99 comment }As mentioned above in the General Rules, if a file does not adhere to these guidelines, stick as closely as possible to that file's way of commenting. Also, whatever style you choose for commenting, stick with it (don't mix and match).
Example:
if (a == 10) { b = true; // Don't start with C99 style single line comments } else { b = (a < 5 && a > 2); /* then switch to C89 style in other areas */ }
This does not preclude the use of other commenting types, it is OK to mix and match commenting types within a file.
Example:
if (a == 10) { b = true; // Single line C99 style comment /* * Mixed with longer C89 block style comments * is completely OK, just don't overly use * mix-and-match style. */ }As stated above however, if single line comments are C99 style and multi line comments are C89 style, do not then switch to a C99 multi line comment and C89 single line comment. This is to avoid the jarring effect that mixed style commenting can have visually while reading the code.
It is also important to comment your data (variables) when it is not obvious as to what they do or in the event you are declaring a global variable or defining a member variable within a class.
Lastly, always remember, you never know who will be looking at the comments, so try to avoid any asinine comments or comments that are insulting or degrading.
Since various tools have the capability to parse JavaDoc/Doxygen[4] style comments,
as well, the custom Omni documentation tool expects the function comments to be in this style, all functions will
utilize the JavaDoc style of commenting to document the function.
Each public facing function that can be utilized by user code will use the following template for commenting:
9. NamingEach public facing function that can be utilized by user code will use the following template for commenting:
/** * @brief Brief description. * * @details A more detailed description of the function. This should describe what the function * is supposed to do in more detail; it may not always be a longer description as some * functions can be explained easy enough in the @brief. * * @return [optional] A return value if any. * * @param [name] [optional] Each function parameter should be marked with this; this is the description * of the input value, its range, and any other info. The [name] is the variable * name in the function. * * @tparam [name] [optional] Each template parameter should be marked with this; this is the description of * the template parameter and what it represents in the function. The [name] is * the template parameter name in the template declaration. * * @exception [optional] Any errors (or error conditions) specific to the context of the function. This * should explain what error conditions can occur (e.g. division by 0, invalid * range, etc.). This should also detail what Omni specific exceptions can be * thrown in the code. * * @warning [optional] Any extra considerations to be aware of. This should explain any pre/post * conditions to be aware of, especially if the conditions should cause * undefined behavior. * * @attention [optional] Any platform specific notes. This should explain any notes to be aware of * if there are differences between certain architectures or platforms, like * the differences that can arise between Windows and Linux, or for systems * that might be big-endian versus little-endian. * * @note [optional] Any notes to be aware of (like system calls, order of operations, etc.). * This section can be used for any addendum's specific to the function that * other sections might not adequately cover or be appropriate in that section. * * @invariant [optional] This is the complexity of this function (e.g. O(1) for X conditions, etc.). */
Naming of variables and functions can sometimes lead to easier to understand code than any amount of documentation
might provide. As such, naming is more important to code use than documentation is.
Names should be descriptive and as short as possible. Local variables (those within function bodies only) can be even shorter and less descriptive if they are extremely limited in use. For instance, in a
The following is the naming convention to be used with this document:
Private member variables (not functions) should start with
Private member functions should start with
File level variables or functions (those only declared within a single program file) will follow the naming convention based upon what level of access that variable or function is supposed to have.
Hierarchical types (
Avoid names that might conflict with various standard library names (like calling a class
9a. File NamesNames should be descriptive and as short as possible. Local variables (those within function bodies only) can be even shorter and less descriptive if they are extremely limited in use. For instance, in a
for
loop, one
could simply declare for (int i = 0; i < 10; ++i)
versus some long name convention
for (int loop_counter; loop_counter < 10; ++loop_counter)
. In contrast, public variables should be
as descriptive and short as possible. For instance int current_thread_count
is well more descriptive
than something like int tcount
.The following is the naming convention to be used with this document:
- Do not use Hungarian notation (types can change)
- Names with leading and/or trailing underscores are reserved for system purposes and should not be used for any user-created names (does not apply to private members).
#define
constants and variable constants should be in all CAPS.- Enum constants are CAPITALIZED.
- Functions,
typedef
values, and variable names, as well asstruct
,union
, andenum
type names should be lower case with an underscore for any spaces, example:get_current_pid
, orset_user_name
- Many macro functions are in all CAPS. Some macros (such as getchar and putchar) are in lower case since they may also exist as functions. Lower-case macro names are only acceptable if the macros behave like a function call, that is, they evaluate their parameters exactly once and do not assign values to named parameters. Sometimes it is impossible to write a macro that behaves like a function even though the arguments are evaluated exactly once.
- Avoid names that differ only slightly, like
foobar
andfoo_bar
. The potential for confusion is considerable. - Avoid names that can look like each other. On many terminals and printers, 'l' (lowercase
ell
), '1' (number one) and 'I' (capitaleye
) look quite similar. A variable named 'l' is particularly bad because it looks so much like the constant '1' (due to font variations).
Private member variables (not functions) should start with
m_
then the variable name to identify it is a
member level variable.Private member functions should start with
_
then the function name to identify it is a private member
level function.File level variables or functions (those only declared within a single program file) will follow the naming convention based upon what level of access that variable or function is supposed to have.
Hierarchical types (
class
, struct
, enum
, etc.) should be named according to what
they will contain within them and to some extent what they will do. The naming of the type shall follow the standards as
listed above regardless of scope.Avoid names that might conflict with various standard library names (like calling a class
cout
or variable
stdin
).
File names should be all lowercase with valid characters being 0-9, a-z, and the underscore "_".
The following extensions will be used for the specific types:
The file names should reflect the functionality defined/implemented in that file.
Files with logical connections (e.g. pairs of header and source files) should reflect that connection in their names wherever possible.
Files belonging to the same module should reflect that dependency by a short unique prefix to the filename, followed by an underscore. The following illustrates this convention:
10. Continuation LinesThe following extensions will be used for the specific types:
.hpp
- header file for C++.cpp
- C++ based source file.hxx
- C++ based source file internal to the libraryThe file names should reflect the functionality defined/implemented in that file.
Files with logical connections (e.g. pairs of header and source files) should reflect that connection in their names wherever possible.
Files belonging to the same module should reflect that dependency by a short unique prefix to the filename, followed by an underscore. The following illustrates this convention:
Makefile io_base.cpp server.c io_base.h server_ioplug.h io_file.h server_ioplug.c io_file.cpp io_cwrapper.h io_stream.h io_cwrapper.c io_stream.cpp
Sometimes an expression will not easily fit on a single line, such occurrences are especially likely when blocks
are nested deeply or long identifiers are used. If this happens, the expression should be broken after the last
comma in the case of a function call (never in the middle of a parameter expression), or after the last operator
that fits on the line. The next line should be further indented by 2 spaces or lined up with the first parameter
on the above line. If they are needed, subsequent continuation lines should be broken in the same manner, and
aligned with each other. The next line of a continuation line should only ever start with a parameter name, not
any type of operator or punctuation (except for a closing punctuation like a closing parenthesis or brace).
Example:
11. Variable InitializationExample:
if (long_logical_test_1 || long_logical_test_2 || long_logical_test_3) { statements; } // long_identifier_term3 is 5 spaces out instead of 2 to showcase // alignment of parameters with each other a = (long_identifier_term1 - long_identifier_term2) * long_identifier_term3; // showcase putting closing parenthesis on single line function( long_complicated_expression1, long_complicated_expression2, long_complicated_expression3, long_complicated_expression4, long_complicated_expression5, long_complicated_expression6 );
Variables should be initialized when defined, unless initialization will cause additional resource
usage when unnecessary.
Example:
Or consider the following:
Multiple assignments are OK as well in the case when doing so does not sacrifice readability.
Example:
In the last portion, it would be better to have declared
The variables that are being multiply assigned should all be of the same type (or all pointers being initialized to
Do not use multiple assignments for complex expressions.
Example:
11a. Constructor LogicExample:
int x = 0;
some_struct s;
if (someval > 0) {
// Causes resources to be consumed and is only needed if someval
> 0
object o;
}
Or consider the following:
object* s = null; // declare but nullify for (int i = 0; i < some_var; ++i) { // object s(i); // WRONG: (named resource in tight loop) s = new object(i); // OK: named vars take resources vs. memory allocation s->some_function(); s->some_other_function(); delete s; }
Multiple assignments are OK as well in the case when doing so does not sacrifice readability.
Example:
int x, y, z, i, a, b, c; // temporary variables x = y = z = i = a = b = c = 0; // OK int rad, max, min, a, b, c; rad = max = min = a = b = c = 0; // Valid but unclear about rad, max and min are
In the last portion, it would be better to have declared
rad
, max
, and min
on
separate lines with comments explaining what each is.The variables that are being multiply assigned should all be of the same type (or all pointers being initialized to
NULL
).Do not use multiple assignments for complex expressions.
Example:
foo_bar.fb_name.firstchar = bar_foo.fb_name.lastchar = 'c'; // Not easy to read!
Class members should be instantiated in the member initialization list when feasible and in the order they are declared
in your class to clarify this is the order in which they will be initialized by the compiler (actual order does not matter
but this increases clarity in the event a member is a class/struct type and thus might have more initialization code that
needs to be considered).
12. Macros, Enums and Constants
Names of macros defining constants are to be all CAPS, example:
CAPITALIZED macro names are appreciated but macros resembling functions may be named in lower case. Generally, inline functions are preferable to macros resembling functions.
Things to avoid when using macros:
13. Error handling and Exceptions#define CONSTANT 0x12345
CAPITALIZED macro names are appreciated but macros resembling functions may be named in lower case. Generally, inline functions are preferable to macros resembling functions.
Things to avoid when using macros:
- Macros that affect control flow, example:
#define FOO(x) \ if (blah(x) < 0) \ return -EBUGGERED;
- Macros that depend on having a local variable with a magic name, example:
#define FOO(val) bar(index, val)
- Macros with arguments that are used as l-values, example:
FOO(x) = y;
- Forgetting about precedence: macros defining constants using expressions must enclose the expression in parentheses.
Beware of similar issues with macros using parameters, example:
#define CONSTANT 0x4000 #define CONSTEXP (CONSTANT | 3)
- Forgetting about macro expansion, example:
#define FOO(x) std::cout << "X = " << FOO(10); // ERROR
In thisFOO(10);
would expand out tostd::cout << "X = " << ;
.
Error handling and catching exceptions can be extremely useful when debugging applications to ensure stable code.
As such, they can have a tendency to be used as a way of control flow versus their original intent (to alert of a
critical error within the application and possibly recover from it). Error handling should not be used as a way of
controlling program flow, but instead a way of being notified when an area of code has done something it should not
have and as a way of cleaning up after oneself.
14. Optimizations
There are many optimizations that a compiler can, and should, make in ones code. It is not advisable, however, to trust and
expect the compiler to do what you want the code to do when you can explicitly tell the compiler what to do via your code.
As an example, most of the time the compiler should optimize the
Additionally, it should be the goal to have code that is as CPU and memory efficient as possible; this means ensuring that the code is as optimized to the C++ standard as possible, thus ensuring that any compiler will adhere to the micro optimizations in the code.
Ways to achieve these standardized optimizations include, but are not limited to, preferring the
15. Appendix A: Other ConsiderationsAs an example, most of the time the compiler should optimize the
i++
post increment operation to be a pre
increment operation (i.e. ++i
) which is more efficient. However, this is a simple optimization that can be done
by the human coder to explicitly define how the code should operate without relying on the optimizations of the compiler; this
ensures that no matter the compiler, the same level of optimization is done across all platforms and architectures.Additionally, it should be the goal to have code that is as CPU and memory efficient as possible; this means ensuring that the code is as optimized to the C++ standard as possible, thus ensuring that any compiler will adhere to the micro optimizations in the code.
Ways to achieve these standardized optimizations include, but are not limited to, preferring the
switch
statement
to a large nested if..else
branch where feasible, using the pre-increment operator where applicable, specifying
inline
for single line and simple functions, and using idioms like return value optimization (a.k.a.
copy elision).
Please note that while there are many ideas and concepts covered in this outline, not everything can be taken into account.
This document is meant as a general guideline and not as a must. If you're making a 10 line program to do a very simple task,
following these guidelines may be more time consuming than is needed to understand the simple program. Use your best
judgment when creating new code.
Try and avoid 'non-standard' standards. For example, as of 1995, C++ adds
Notice the
Try and avoid 'non-standard' standards. For example, as of 1995, C++ adds
#define
values of
and
, or
, not
and a couple of others (as defined in the header ciso646
)
that are literal translations (definitions) of their respective values. In other words in the ciso646
header
file you would have the following:
#define and && #define or || #define not ! #define bit_and & #define bit_or |Along with some other definitions for some comparison and bitwise operators. So one could technically do the following:
int x = 10; int y = 10; bool z = false; if (x == 10 and y == 10) { if (not z) { // do something } else { // do something else } }
Notice the
and
in between the x
and y
comparison statements? This is the same as
saying x == 10 && y == 20
. So even though the macro definitions of these value or actually considered a
'standard' within the C++ standards, it might confuse those less privy to such values. We say this because the
actual and
operator itself (the double ampersand &&
) is pretty common among many languages,
making it easier to fully understand that this is an actual comparison operator, while the name and
itself might
be a reserved keyword to function as the same as the &&
operator, or could it could potentially function
as a bitwise and
operator (&
). The implementation of the actual and
keyword or
name is language dependent. Hence why we say 'non-standard' standard, because though it is an actual 'standard' of the
language itself, it's not as widely used as other nomenclature, as such it's potential for ambiguity is much higher.
To this effect we advise against these constructs unless absolutely necessary.
References