Coding Styleguide¶
- Table of contents
- Coding Styleguide
Those who have worked on the TSC project will remember the dreaded discussion about coding style. This project will avoid the trap by using a consistent coding style right from the start. It is outlined in this document.
The game's source code is formatted mostly according to the 1TBS style, which is a variant of K&R, with slight adjustments. Details are documented below. Most (not all!) rules can be followed by formatting with the astyle command like this:
$ astyle --style=1tbs --indent=spaces=4 --indent-namespaces \
--attach-classes --attach-namespaces --attach-closing-while --attach-inlines --attach-extern-c \
--align-pointer=type --align-reference=type \
--break-one-line-headers --add-braces --close-templates --lineend=linux \
yourfile.cpp
In general, try to keep your code readable. Specifically, it is often useful to leave empty lines to separate logically grouped statements from one another.
One of the design goals of this document is to keep the styling consequent and easy to follow. For instance, instead of the questionable distinction of attaching braces to structs, but not to classes in the 1TBS style, this style says to simply always attach the braces (the only exception being function definitions). In so far it includes elements from the Stroustrup style. Likewise, indenting every brace block is easier to remember than not to indent namespaces.
File encoding¶
All source code files have to be encoded in UTF-8.
Commentary¶
Semantically, comments should normally reflect why code is written the way it is. Normally it is not required to explain what code does, unless it is an exceptionally complex part. Syntactically, use //
for one and two lines of comments. Starting with the third line, comments should use the bock syntax /* ... */
. In the block syntax, align each star with the one on the preceeding line. Terminate the comment block on the last line of the comment.
// Careful: this is used in foobar() as well
/* This code is designed to specifically fit the purpose of an example.
* It was not at all easy to come up with all this text, but for the
* sake of an example, it was required to do so. */
Where Doxygen is used to generate documentation, use Doxygen's ///
markers for short and the /**
markers for long documentation. Other than that, the above advice applies.
Line endings¶
All files use only LF as line endings (unix-style line endings).
Indentation and line length¶
Source code is indented with 4 spaces, tabs are not used. All blocks enclosed in braces are indented, including namespaces. Do not indent labels, including visibility labels like public
and private
; they should line up with the enclosing statement. Preprocessor statements are not indented.
#ifndef MY_HEADER
#define MY_HEADER
namespace MyNamespace {
class MyClass {
public:
MyClass();
~MyClass();
};
struct MysSruct {
int a;
int b;
};
}
void myFunc()
{
foo();
if (something) {
bar();
baz();
}
}
#endif
Lines should be broken around 80 characters, and the resulting continuation lines should be indented so that it makes sense to look at. Example:
if (somecondition) {
thisIsAVeryLongFunctionName(this_is_a_parameter,
this_is_another_parameter,
third_parameter);
}
This is not a hard requirement. You should use whatever conveys the meaning best. That's why the astyle
command above does not include a hard line wrap option.
Placement of braces¶
Braces are placed on the same line like the statement they belong to, be that a class or namespace declaration, an extern C
, or anything else. The only exception from this are ordinary function definitions: for them the opening brace is broken into the next line. In any case, the closing brace always has its own line (makes it easy to spot the end of a block). if/elseif/else
is cuddled to keep code compact, and the rare case of a trailing while
has the while
attached to the brace. Do not leave out braces even for one-line statements. This should prevent any accidental cutting of conditional clauses. To keep style consistent, also do not use all-in-one-line conditionals (this violates the expectation that the closing brace for each block can be found on its own line at the relevant indentation level).
Keen readers will notice that for in-function statements this uses the 1TBS style, and for out-of-function statements the Stroustrup style. Examples below.
// Exception: function definition
void main()
{
// ...
}
// Member functions also fall under the exception.
void Foo::setX(int x)
{
m_x = x;
}
// Everything else: attach the braces.
class Foo {
// ...
};
struct Foo {
// ...
};
enum class Foo {
// ...
};
if (condition) {
// ...
}
while (condition) {
// ...
}
// A function definition inside a class definition does not count as an "ordinary" function. Attach.
// This is mostly useful for defining short class constructors or getters/setters.
class Foo {
Foo(int x) : m_x {}
inline int getX() { return m_x; }
};
// Trailing while attached
{
// ...
} while (condition)
// Brace kuddling from 1TBS
if (condition1) {
// ...
} else if (condition2) {
// ...
} else {
// ...
}
// Required braces around one-line statements
if (condition) {
doit();
}
// Also requires braces
if (condition1) {
doit();
} else { // Thanks to brace cuddling, this is not as bad as in pure K&R
doother();
}
// DO NOT DO THIS
if (condition) { oneliner(); }
// Break it.
if (condition) {
oneliner();
}
Initial assignment¶
Assign an initial value to any variable you declare. This prevents undefined values from coming from a forgotten assignment and eases debugging quite a bit, because it obviates the question "is this a real value or was this variable never assigned?".
int a; // BAD
int a = 0; // GOOD
Pointer and reference alignment, multi-variable declarations¶
Place the *
and &
next to the type, not to the name. They belong semantically to the type. Do not declare multiple variables in one line if one of them is a pointer, it causes confusion:
int i = 0, j = 0; // Okay, no pointer
int* p = nullptr; // Pointer to int
int* p1 = nullptr, p2 = 0; // DONT DO THIS. p2 is not a pointer!
// Instead, break the lines up.
int* p1 = nullptr;
int p2 = 0;
Multiple variable assignments should be aligned at the =
.
int foo = 0;
int foobar = 0;
int la = 0;
Parantheses and spacing¶
Between a keyword and the opening paranthesis is exactly one space. Between the closing paranthesis and the opening curly brace is exactly one space as well. There is no space between a function name and the opening paranthesis of its argument list.
Template angles and index brackets do not have any surrounding space.
void foo(int myparam) // No spaces around the "(" and ")"
{
if (condition) { // One space between keyword if and "(", and one space between ")" and "{"
myFunc(5); // No spaces around the "(" and ")"
}
}
vector[3] = foo; // No space between "vector" and "[3]"
map<string, vector<int>> mymap; // No spaces around <> (C++11 syntax)
Case of identifiers¶
- Macros are ALL_IN_CAPS. They need to stand out, because macro expansion can have surprises which may explain cryptic error messages.
- Variable identifiers use snake_case, including identifiers for variables declared
const
and/orstatic
. The different variable types are distinguished by the prefix (see below). - Function identifiers use snakedCamelCase (i.e. first word is lowercase, the rest is CamelCase). Constructors and destructors are the only exception to this rule (because C++’ syntax requires them to be the same way as the type identifier).
- Type identifiers (class, enum, struct identifiers) use CamelCase.
The different casing rules make it easy to spot which kind of identifier one is dealing with. Between one-word functions and variables a distinction is not possible except for the variable prefix; it is expected that most function identifiers are composed of multiple words, so that this is not an issue.
#define THIS_IS_A_MACRO(x) foo_ ## x
struct MyStruct;
enum class MyEnum;
class MyClass {
MyClass();
~MyClass();
void memberFunction();
static int staticMemberFunction();
int m_member_variable;
static int static_member;
};
void foo()
{
static int local_static_variable;
float normal_local_var = 3.5f;
// ...
}
Abbreviated Hungarian Notation¶
Identifiers of variables and constants begin with a short sequence of characters that encodes some important information about the variable in question. This is called Hungarian Notation, but in full, it is cumbersome to read and leads to long identifier names. The following prefix characters have been chosen with respect to two goals: Make variable scope immediately visible, and warn of "unusual" types.
Prefix | Meaning |
---|---|
No prefix: Local variable | |
m | Member variable |
f | File-local variable |
g | Global variable |
p | Variable holds a pointer (both raw and managed pointers) |
a | Variable holds a raw array (not: vector or other C++ containers) |
The scope prefix comes before the type prefix. Thus, mp_foo
is a member variable holding a pointer, and ga_argv
is a global variable holding a raw C array.
There are two special cases. First, member variables of structs and enums do not have a leading m
prefix, because they do not normally contain functions, but are only accessed from the outside (whereas for classes as per the secrecy principle access to member variables from the outside is unusual), and it would be cumbersome to always add the extra m
. Second, static member variables of classes do not have a scope prefix. Instead, they are always to be accessed via the class name.
static int f_something; // File-local variable
extern int g_globvar; // Global variable
class MyClass {
void myFunc() {
int* p_int; // Local variable
m_normal_member += "AAA"; // Accessing member variable
doSomething(MyClass::foobar); // Exception: accessing static member variable via class name, not directly
}
std::string m_normal_member; // Normal member variable
int* mp_int; // Member variable with pointer
static const float foobar = 42; // Exception: Static member variable
};
struct Point {
int x; // Struct members have no "m" prefix
int y;
int z;
owner* p_owner; // But they do have the type prefix if required.
};
Point moveUp(Point p)
{
p.y -= 10; // Access to struct member without "m"
return p;
}
enum class Color { red, green, blue }; // Exception: enum members do not have "m"
void myFunc(color c)
{
if (c == Color::red) { // Because they are accessed only from the outside.
// ...
}
}
enum specifics¶
Names of enum
identifiers are singular, not plural. If used as a type, color var
reads more natural than colors var
. Use enum class
instead of raw enum
whenever possible (this is C++11 specific and allows colliding enum identifiers in case you wonder that enum class
is valid syntax).
enum class Color { red, green, blue };
void foo(Color c)
{
// ...
}
Use of namespaces¶
If the STL is used, always include the STL namespace at the top of the .cpp
file. The same goes for internal namespaces (that is, those that are defined by the game itself).
#include <iostream>
using namespace std;
using namespace InternalNameSpace;
void main()
{
cout << "Doing something" << endl; // Actually std::cout and std::endl
doIt(); // Actually InternalNamespace::DoIt()
}
Library namespaces should normally be spelled out in full, unless they are exceptionally long. In that case, abbreviate them with namespace
at the top of the .cpp
file.
namespace LLN = LongLibraryNamespace;
// ...
LLN::foo var;
If a library namespace is needed really often in a function, you can include it in that function only.
void Foo::someFunction()
{
using namespace MyLib;
// All the below functions come from the MyLib namespace
doA();
doB();
doC(0);
doD("foo");
}
File names¶
All source code files are in snake_case. C++ source code files end in .cpp
, C++ headers end in .hpp
. C source files end with .c
, C headers in .h
.
Guidelines for headers¶
Header guards¶
All headers use header guards. The header guard is composed from the filename without the file extension coverted to CAPITAL_LETTERS.
// my_file.hpp
#ifndef MY_FILE
#define MY_FILE
// ...
#endif
Namespaces in headers¶
Namespaces in headers are always spelled out in full and not abbreviated or included. This includes the STL namespace if it is used. This way the inclusion of a header does not define unexpected new namespaces.
namespace MyNamespace {
class MyClass {
MyClass();
std::vector<std::string> m_my_values;
}
}
Forward declarations in headers¶
To increase compile times and keep the inclusion graph simple, headers should try hard to not require other internal headers. If an internal type is required in a header, it should be forward-declared in that very header. Note that it is possible to forward-declare classes, structs, and even enums. In most cases, forward-declarations are entirely sufficient. For instance, pointers, references, many templated types, and even smart pointers can be used with only the forward-declaration available.
// Forward declarations
class MyClass;
class MyOtherClass {
public:
MyOtherClass(MyClass& mc) : m_mc(mc){}
void foo(MyClass* p_mc){ /* ... */ }
private:
MyClass& m_mc;
};
The corresponding .cpp
file will then have to include the internal header for MyClass
:
#include "my_other_class.hpp"
#include "../misc/my_class.hpp" // <---
Inclusion of headers¶
Each C/C++ source file includes only the headers it needs to compile, and all inclusions are at the top of the file. Inclusions are done in this order:
- The header file corresponding to this
.c
/.cpp
file. Doing this first ensures that the.hpp
file is self-contained, because on compilation of the corresponding.cpp
file the compiler will error out on the very first#include
line then if the header is not self-contained. For.hpp
files, this step is obviously missing. - Other internal headers.
- External library headers.
- Standard library headers.
The path delimiter for #include
statements is always a forward slash, because this compiles on both Windows and Unix systems. External and standard library headers are included with angle #include <>
, internal headers with quoted #include ""
.
// This is foo.cpp, it has a header foo.hpp.
#include "foo.hpp"
#include "../misc/internal_header.hpp"
#include <curl.h>
#include <vector>
#include <cstdlib>
Updated by quintus over 3 years ago · 17 revisions