This is a style guide for use in the C! codebase. Follow this document when adding or updating code in C! or its associated projects.
C! maintains a strict coding style. This may seem tedious to some, but strict discipline enhances maintainability and ultimately saves time. Some of the style choices are not conventional, but each choice is made for a good reason. C!'s style enhances readability by fitting more code vertically and improves the developer's ability to compare code with the 80-column limit, which allows up to three files to be viewed side-by-side on a modern screen.
- Consistency
- Add just enough space to enhance readability
- Avoid unnecessary syntax
- Don't Repeat Yourself (DRY)
- Don't add code you might need later
- C++ headers use the
.hextension. - C++ implementation files use the
.cppextension. - X-Macro definitions use the
.defextension. - C++ header and implementation files must match the C++ class name.
- There should be one header and one implementation file for each non-trivial class.
- Use
#pragma onceinstead of#ifdef NAME_Hin headers.
- Order includes:
- Local header includes with double quotes (e.g.
"SmartPointer.h"). - C! project headers
<cbang/...>(when applicable). - C++ standard headers (
<vector>,<string>, ...). - C headers, prefer C++ versions.
- Local header includes with double quotes (e.g.
- Keep include lists grouped and separated by a blank line.
Example:
#include "ColumnDef.h"
#include "Statement.h"
#include <cbang/SmartPointer.h>
#include <cbang/util/StringMap.h>
#include <vector>
#include <unistd.h>- Enums / classes / structs: PascalCase (e.g.,
TableDef,PCIInfo). - Member variables, functions and methods: lowerCamelCase (e.g.,
dbName,maxKeyLength). - Alias names: end with
_t(e.g.,columns_t). - Macros and preprocessor symbols: ALL_CAPS with underscores.
- Indent with 2 spaces.
- Do not use tabs.
- Enforce a strict 80 column limit. Break long lines at logical boundaries.
- Do not leave extra whitespace at the end of lines.
- Binary operators
- Place one space before and after binary operators:
a + b,x == y,i <= n.
- Place one space before and after binary operators:
- Unary operators
- No space between a unary operator and its operand:
-x,!flag,++i,--i.
- No space between a unary operator and its operand:
- Pointer/reference declarations
- Space between type and
*/&, and no space between symbol and variable name:BIGNUM *bn;const std::string &s;
- In expressions, write
*ptrand&obj(no space).
- Space between type and
- Function calls and declarations
- No space between a function name and the opening parenthesis:
foo(x, y). - Space between control keyword and
(:if (cond),for (int i = 0; i < n; i++),while (expr). - Do not add spaces immediately inside parentheses:
f(a, b), notf( a, b ).
- No space between a function name and the opening parenthesis:
- Parentheses and casts
- Use
static_cast<T>(expr)etc., with no extra spaces inside< >or( ).
- Use
- Array subscripts
- No space before
[or between brackets and index:arr[i],buf[0].
- No space before
- Commas
- No space before a comma, a single space after:
f(a, b, c). - In long argument lists, break after a comma.
- No space before a comma, a single space after:
- Semicolons
- No space before
;. Inforheaders, use a single space after each;component:for (int i = 0; i < n; i++).
- No space before
- Member access
- No spaces around
.or->:obj.method(),ptr->field.
- No spaces around
- Ternary operator
- Use single spaces around
?and::cond ? a : b.
- Use single spaces around
- Colons (labels, constructor init)
- In constructor initializer lists keep a single space before and after
::Info(Inaccessible) : maxKeyLength(0) {}
- In constructor initializer lists keep a single space before and after
- Templates and angle brackets
- No spaces immediately inside angle brackets:
std::vector<int>. - Space after commas in template parameter lists:
std::map<Key, Value>.
- No spaces immediately inside angle brackets:
- Range-based for
- Single space around
::for (auto &v : container).
- Single space around
- Line breaks and continuations
- Prefer breaking at commas or open parenthesis when a line is long.
- Indent continuation lines by 2 spaces relative to the previous block.
- There should be blank lines between functions.
- Single-line functions and methods have no space between them.
- Opening braces on the same line.
- Place
elseandelse ifon same line as the preceding brace. - Add a blank line before a
} elsebut not before a bareelse. - Do not add braces for a single child statement unless it prevents a dangling else.
if (a) {
foo();
bar();
} else if (b) {
baz();
while (true) bla();
} else bas();if (a) {
if (b) foo();
else bar();
}- Small single-line single-statement functions should be written on one line. Place the statement immediately after
{with no leading space:
iterator begin() const {return devices.begin();}- For multi-line functions, put each statement on its own line and indent:
void URI::parsePair(const char *&s) {
string name = parseName(s);
string value = consume(s, '=') ? parseValue(s) : "";
set(name, value);
}- Use initializer lists; place a single space before
:and a single space after:before initializers:
Info(Inaccessible) : maxKeyLength(0) {}- Initializer order must match declaration order.
- Default access is private. Common layout:
- Data members (private by default)
- typedefs / using aliases
- public: interface
- protected: helpers (when needed)
- Place pointer
*and reference&next to the variable name, leaving a space between the type and the symbol:
BIGNUM *bn;
const std::string &s;- Use
0for null pointer literals; do not useNULLornullptr.
- Use
if (x)orif (!x)instead ofif (x != 0)orif (x == 0).
- Prefer
<and<=operators.
- Do not wrap return expressions; e.g.
return x + y;notreturn (x + y); - Do not wrap operators when precedence rules are sufficient; e.g.
if (x * y == 5)notif ((x * y) == 5).
- Use
unsignedfor counts and non-negative sizes in the API (the repo commonly usesunsigned). - Use fixed-width integers (
uint16_t,uint8_t,uint64_t, etc.) when interfacing with hardware, binary formats, or external protocols.
- Use
usingrather thantypedef:
using iterator = ListImpl::const_iterator;- Use
overridefor overridden virtual functions:
void callback(state_t state) override;- Use exceptions for error reporting in library code.
- Keep error messages clear, and avoid swallowing exceptions silently.
- Minimize macros.
- Macro names upper-case, and clearly documented.
- When generating code with macros, provide a corresponding cleanup section (undef).
- Do not add
usingnamespace directives in headers. - All code must be inside a namespace.
- Use anonymous namespaces instead of
staticfor internal linkage:
namespace {
void foo() {...}
void bar() {...}
}- Mark functions
constwhere applicable.
- Use
autokeyword to replace complicated type declarations where possible.
- Use C!'s
LOG_*macros.
- Avoid threads.
- Use the event system.
- Threading is only acceptable for backgrounding CPU intensive tasks.
- Avoid locking, use events and message passing.
- The code should not produce any compiler warnings with the default settings.
- Prefer concise inline comments that explain "why", not "what".
- Don't waste the reader's time.
- Every source/header file begins with a license/copyright block.
- Add unit tests to prevent future regressions.
- Any changes must pass or fix all unit tests.
- Test one thing at a time, within reason.
- Each test must complete quickly.