C and C++ Coding Standards
Synopsis
This document is intended to provide a base standard for all languages derived from C and C++ that we work with at Appcelerator, allowing us to write clean, consistent-looking, readable code. Due to the lack of well-defined common C coding standards, we have grown our own from scratch, as opposed to the Java and Objective-C standards, which are based on well-defined existing standards that have modifications made to them.
This document does not include platform- or product-specific conventions for C or C++.
Standards
The following is a set of standard formatting and naming conventions which we expect to be followed for C or C++ source code.
Column width
We do not enforce column width. However, you should make a best effort to keep your source readable on 15" monitors, which have approximately 200 columns (based off of Andale Mono 12pt.)
Indentation
- Indentation is done in tabstops (
\t
) to allow for configurable spacing in modern IDEs. For the purposes of all indentation, one tabstop is considered to be 4 spaces.
Whitespace
Alignment
- All alignment must be on tabstops.
There are two types of comment styles: Comments formatted for internal documentation, and comments formatted for documentation generation.
Documentation generation
- Comments must follow the general formatting rules below, except for where they conflict with the documentation generation rules (appledoc requires that the comment open with
/**
on a single line to be processed, for example, and some may not allow for leading splats or require other keywords.)
Internal documentation
- Commenting style:
- Methods or functions are documented with
/* ... */
, where each line has aligned splats
- Code is documented with aligned
//...
statements
- Commented-out code is documented with
/* ... */
/*
* Describe your function here
*/
void myfunctin()
{
//
//
//
do something here
//
//
//
do something else really complex
/*
* Why didn't we delete this?
*
dead_code()
*/
}
Forbidden words
The following words are not allowed in comments:
- hack
- temporary
- workaround (exception: In conjunction with an explanation of an issue we have no control over)
- "implement later" or other TODO-equivalent statements (use TODO)
- Any words you would not say in front of a nice grandma
- "Why do we...?" (check the blame, ask the original committer, and document or remove the questionable code)
- Words or phrases which could be considered as detrimental to the platform
In addition it is expected that all of your comments will contain correct spelling (and reasonable grammar for the intent of the comment)
- Exposed API (public, protected, or a C function in a header file)? Write a comment, and make sure the docgen can pick it up.
- Done something strange? Write a comment.
- Need to write pseudocode or logic flow? Write a comment.
- Handling a unique edge case? Write a comment.
- Is your code a platform "workaround" of some kind? Write a comment.
- Did you have to ask a colleague about the intent of code they wrote? Write a comment for it.
- When in doubt? Write a comment.
- If your comment has absolutely nothing useful to say about the source, don't write a comment.
- If your comment is redundant, don't write a comment.
- If your comment explains something obvious in the source, don't write a comment.
Braces
- Closing braces must be on their own line, and followed by whitespace.
- Exception: Closing braces immediately followed by another closing brace are not followed by whitespace.
- Opening braces must be on their own line, aligned with the previous statement.
- Opening braces should not be followed by whitespace.
void f(x)
{
if (x)
{
// ...
//
//
//
}
else
{
// ...
}
while (true)
{
}
}
namespace
NEVER use "using namespace x". This include "std" in C++
namespace ordering
Namespaces should follow the following declaration/definition ordering, with each section separated by whitespace:
namespace-contents:
namespace
const
variables
enum
- typedefs (including
struct
/ union
)
- function declarations
- inlined functions
- class information
- definitions
- implementation (inline & template)
- namespace-contents
namespace implementation
When using a namespace in a implementation file, use the following style:
// this is what we want
namespace
{
MyType1 myFunc1(MyType2 x, MyType3 y)
{
// impl
}
}
//here is what we don't want (even worse if you have nested namespaces):
namespace::MyType1 namespace::myFunc1(namespace::MyType2 x, namespace::MyType3 y)
{
// impl
}
Headers
Order of declarations
Headers should have their definitions/declarations ordered in the following way (with namespace-contents
as above), with whitespace between sections:
- copyright notice
- header include check
- system
#include
- 3rd party
#include
- project
#include
- macros
- namespace-contents (same ordering of contents,
namespace
optional)
Preprocessor
The preprocessor deserves its own detailed section. However, you are discouraged from using the preprocessor except where absolutely necessary.
Naming
Macro names must:
- Be in all caps
- Have words separated by underscores
- Be descriptive
Indentation
All preprocessor directives must not be indented (start at column 0) although their contents may be aligned with surrounding code.
The include Directive
- For system headers:
#include <...>
- For project/3rd party headers:
#include "..."
Constants
Do not define global constants with #define
. Use const
variables only, and ideally constants are in a namespace
as well (when writing C++.)
The define Directive
- Do not include a semicolon at the end of a
#define
- Use
#define
only for situations in which you cannot use const
or inline
functions. Such examples may include:
DEBUG
or other conditional-compilation checks
- Function-like calls which cannot be easily abstracted into functions (such as our Objective-C macro
RELEASE_TO_NIL
)
- Function generators (such as
getX
/ setX
pairs, or defining a function which returns a constant value)
#define
statements which perform any sort of inline operation must be enclosed in parenthesis
Multi-line #define
statements are expected to be formatted according to the usual rules, with the first statement of the #define
not being indented.
#define NAMED_PROPERTY(name) \
void* name = NULL; \
void set_##name(void* value) \
{ \
name = value;
} \
\
void* get_##name(void) \
{ \
return name;
}
Conditionals
- Prefer
#ifdef
and #ifndef
over #if defined(...)
and #if !defined(...)
.
- Conditionals should have their contents aligned with surrounding code, or if the conditional does not have surrounding code, their first line should not be indented.
Flow control
switch/case
goto
Usage of goto
is necessary sometimes but each usage must:
- Be preceded by a comment indicating the necessity of
goto
- Use a well-named label
Only use goto
when it is absolutely required and you have no other option.
Looping constructs
Looping constructs follow all the rules for flow control, as far as placement of braces and organization of multi-line conditionals.
Whether or not to use inline variable declarations for any looping construct (but especially for your for
loop iterator(s)) is at your discretion.
Functions
- Function names should not be camel-case; they should have words separated by _.
- No space between the method name and opening parenthesis
- There must be a single space (or newline) between a
,
and the next variable in the arguments list
- Every function is expected to have a declaration. Functions which are to be exported or otherwise publicly available are to be declared in an associated header file; all other functions are declared in the implementation.
- Declarations should include both a type and a name for all parameters.
- Functions which take no arguments do not need to be declared as
f(void)
; can be f()
instead.
- Functions should be named as "actions" rather than things (
get_x()
vs. x()
)
void foo_bar(int x, int y,
int z);
int f();
void foo_bar(int x, int y, int z)
{
// func goes here
}
int f()
{
return 0;
}
return
Functions which return
a compound statement should have that expression wrapped in parenthesis.
Variables
- Variables should not be camel-case; they should have words separated by _.
- Variables should have descriptive names; the exception is for pure iterators, which are allowed to be
i, j, k
.
- Variables must be defined at the beginning of a block.
- Only one variable should be defined per line (no comma operator when defining variables.)
- Only one variable should be assigned to per line (no chained
=
.)
- Variables should be named as "things" rather than actions or gerunds.
- Variable types should follow these rules:
- Pointer types should have the * after the type, followed by a space
- Reference types should have the & after the type, followed by a space
- Array types should have the
[]
after the variable name
- Array types should be preferred over pointer types where the intended use of the object is as an array.
int x;
int* x_ptr;
int& x_ref;
char* argv[];
enum
typdef
- Names of type constructs should be capitalized camel-case
struct and union
Operators
Prefix/postfix increment/decrement
- Prefix increment/decrement is preferred over postfix increment/decrement.
- Make a best effort to avoid usage of these operators in compound statements.
Ternary
Usage of ternary is discouraged, but it is often useful for certain cases (such as assignment or return value based on a condition.) When using ternary, you should:
- Consider ternary to always be a compound statement in and of itself
- Place any compound statements within parenthesis
- Treat both the
?
and :
characters as binary operators
- Avoid obvious side-effects within ternary, unless they are intended.
int f(boolean check)
{
return (check ? (1+1) : 0);
}
Operator overloading
A special word on operator overloading for C++: Avoid operator overloading. Overloading operators leads to interesting scenarios such as this one:
class Vector
{
inline double operator[](int i) {
// return something from impl
};
};
// Don't you even dare think of making Matrix a base class for
// RowMajorMatrix or ColumnMajorMatrix, because the problem below still
// happens every time we abstract either to a Matrix.
//
// Adding a boolean row_major as part of the constructor (and a read-only property)
// is ALSO not acceptable because then every time you read from a matrix, you have to
// check it.
class Matrix
{
// constructor intentionally omitted
Vector representation[];
inline Vector operator[](int i) {
return representation[i];
}
};
Vector a(1,2,3);
Vector b(4,5,6);
Vector c(7,8,9);
Matrix m(a, b, c);
// Without looking at the definition of Matrix::Matrix(...),
// this could be EITHER:
// 3 (row major)
// 7 (column major)
double what = m[0][2];
// Here's the better way to do it (although there's still an operator overload, it makes "more sense")
double is3 = m.row(0)[2];
double is7 = m.col(0)[2];
In the case where you absolutely must overload an operator (such as placement new()
, incrementing enumerators, or creating a functor) then you are expected to provide ample documentation justifying your folly and make every effort possible to prevent other developers from committing crimes such as the above example.
An additional note: You must define both operator+
(void)
and operator
+(int)
when overloading increment (and the equivalent operators when doing decrement) and the definitions must be identical.
C++ rules
These rules are to be applied on top of any C rules described above. If there is a conflict then the rule described in the C++ section takes priority.
Filenames
Classes:
- Permissions should be strict by default and opened up as needed
- Inline methods should be defined in a implementation file and not in the header or class declaration.
- Keep implementation outside of class declaration. The class declaration should be seen as a interface that you can read over quickly in order to understand how to use it.
- If class is meant to be derived from, make sure to include a virtual destructor (a destructor defined for this purpose does not fill the need for the "rule of three")
- Respect the rule of three ("always override operator=, destructor, and copy constructor if you override any").
- Comment if a method is static in method comment within the implementation file
*Remember that virtual methods have time and cost (IE: just think when using them)
General C++
- Multiple inheritance should be avoided in most cases. Inheritance of a single normal class and multiple interfaces (pure virtual class) is allowed. Keep in mind that when using multiple inheritance you are going to draw strong questions and concerns regarding the design - be prepared with very good answers.
- "new" should be used over malloc when possible
- Shouldn't really need to be called out, but NEVER mix malloc/delete and new/free
- Be const correct within reason. It can be a huge pain in the ass but it's less painful in the long run.
- Try to avoid const_cast<>()
- Use function overloading rather than name variants if possible (IE: add() with overloaded versions rather than addInt, addFloat, etc)
- Remember that STL can be expensive. Dont use a sledgehammer to drive a nail into the wall
Other rules
C99
You are expected to use a C99-compliant compiler for all C and C++ code.
C++11
Although you may use a (partially) C+11-compliant compiler, do not use C+11 features in any code.
BOOST
BOOST is illegal. If you make a commit which uses BOOST (and is not a 3rd party library that depends on BOOST, and even then...) you are fired.
Nonstandard extensions
Nonstandard extensions such as blocks or GCC-specific macros/definitions/keywords should not be used unless you are writing code which is intended to be used with compilers supporting these features only and do not intend to port the source to, or use on, other platforms.
If you must use these features, place them within the appropriate preprocessor conditional blocks.
C++ FQA familiarity
When writing C++, you may want to maintain a level of familiarity with the FQA (Frequently Questioned Answers) to avoid grammar ambiguities and recognize inappropriate usage of features. This is especially true when using the FAQ Lite as a reference.
Code clarity
Writing professional, enterprise-level code is not an obfuscation contest. Do not use aggressive, weird tricks for optimization or other confusing-looking code without first:
- Considering all other possible options
- Documenting it
Use your best judgement as to what constitutes "clear code" and expect a general consensus over what constitutes that particular style as the source base evolves.