-
Notifications
You must be signed in to change notification settings - Fork 1.1k
TFS Coding Style Guide
Every developer has their own code style conventions and opinions, but in a collaborative project it's a good idea if every contributor follows the same principles and guidelines.
We follow the Google C++ Style Guide, with some notable exceptions:
- Use tabs instead of spaces for indentation
- Column limit is raised to 120 instead of 80
- Brace wrapping according to the Mozilla C++ Coding style
When in doubt, you can run make format
to format source files according to our coding style (requires clang-format
to be installed)
The following shortcuts are used in this section to denote naming schemes:
- CamelCased: The name starts with a capital letter, and has a capital letter for each new word, with no underscores.
- camelCased: Like CamelCase, but with a lower-case first letter
- under_scored: The name uses only lower-case letters, with words separated by underscores. (yes, I realize that under_scored should be underscored, because it's just one word).
- ALL_CAPITALS: All capital letters, with words separated by underscores.
Every source file should have an associated header file. Prefer .h and .cpp extensions for C++ files.
All of a project's header files should be listed as descendants of the project's source directory without use of UNIX directory shortcuts . (the current directory) or .. (the parent directory). Within each section the includes should be ordered alphabetically. Separate the categories with empty lines.
Write includes in the following order:
- Precompiled header file (
otpch.h
) - only import PCH in .cpp files - The header file associated with the current compilation unit file - only in .cpp files
- Project header files, using quotation marks
- System headers, using angle brackets
Example:
#include "otpch.h" // precompiled header
#include "signals.h" // header for the current compilation unit
#include "actions.h" // other project headers
#include "configmanager.h"
#include "databasetasks.h"
#include <csignal> // system headers
The header related to this source file should be included before other project headers to make sure that it includes anything it needs, and that there are no "hidden" dependencues: if there is, it'll be exposed right away and prevent compilation.
Prefer placing non-member functions in a namespace; avoid global functions and variables.
Global variables should almost never be used (see below for more on this). When they are used, global variables are with a leading g_ added.
int g_Shutdown; // Good: Really need this global variable
Initialize variables in the declaration.
void f(){
int i;
i = f(); // Bad
int j = g(); // Good
vector<int> v = {1, 2}; // Good
}
If you need a static or global variable of a class type, consider initializing a raw pointer (not a "smart" pointer) which will never be freed. Objects with static storage duration, including global variables, static variables, static class member variables, and function static variables, must be Plain Old Data (POD): only ints, chars, floats, or pointers, or arrays/structs of POD.
In C++11 the idea of a POD is to capture basically two distinct properties:
- It supports static initialization
- Compiling a POD in C++ gives you the same memory layout as a struct compiled in C.
You can check if class isPOD with "std::is_pod".
A POD struct is a non-union class that is both a trivial class and a standard-layout class, and has no non-static data members of type non-POD struct, non-POD union (or array of such types). Similarly, a POD union is a union that is both a trivial class and a standard layout class, and has no non-static data members of type non-POD struct, non-POD union (or array of such types). A POD class is a class that is either a POD struct or a POD union.
Do not define implicit conversions. Use the explicit keyword for conversion operators and single-argument constructors.
class Foo {
explicit Foo(int x, double y); // Good
...
};
void Func(Foo f);
Func({42, 3.14}); // Error, as expected
One-argument constructors that are not copy or move constructors should generally be marked explicit.
Use a struct only for passive objects that carry data; everything else is a class.
Always make class methods ‘const’ when they do not modify any class variables.
Prefer small and focused functions.
- Do not be unnecessarily verbose or state the completely obvious. Notice below that it is not necessary to say "returns false otherwise" because this is implied.
// Returns true if the table cannot hold any more entries.
bool IsTableFull();
- When commenting constructors and destructors, remember that the person reading your code knows what constructors and destructors are for, so comments that just say something like "destroys this object" are not useful.
- Comments should be descriptive ("Opens the file") rather than imperative ("Open the file");
- Do not duplicate comments in both the .h and the .cc. Duplicated comments diverge.
- Tricky or complicated code blocks should have comments before them.
All parameters passed by reference must be labeled const.
void Foo(const string &in, string *out);
An out argument of a function should be passed by reference except rare cases where it is optional in which case it should be passed by pointer.
void MyClass::getSomeValue(OutArgumentType& outArgument) const // Good, bad is: doSomething(OutArgumentType* outArgument)
Instead of using a macro to store a constant, use a constexpr value.
Use 0 for integers, 0.0 for floats, nullptr for pointers, and '\0' for chars. Never use NULL.
Also remember about expressions and floating point types:
float x = 1 / 2; // Equals 0
float x = 1 / 2.0f; // Equals 0.5
Use precision specification for floating point values unless there is an explicit need for a double.
// Good:
float f = 0.5f;
float g = 1.0f;
double k = 0.1;
// Bad:
float f = 0.5;
float g = 1.f;
double k = 0.;
Prefer sizeof(varname) to sizeof(type).
Use of auto
is encouraged when it increases readability, particularly as described below. Do not use auto for file-scope or namespace-scope variables, or for class members.
Programmers have to understand the difference between auto and const auto& or they'll get copies when they didn't mean to.
Never assign a braced-init-list to an auto local variable. In the single element case, what this means can be confusing.
auto d = {1.23}; // d is a std::initializer_list<double>
auto d = double{1.23}; // Good -- d is a double, not a std::initializer_list.
Usage of smart pointers (std::unique_ptr
, std::shared_ptr
and std::weak_ptr
) is encouraged. Avoid using new
and delete
.
Avoid using Run Time Type Information (RTTI): dynamic_cast, typeid.
Use C++-style casts like static_cast(double_value) or brace initialization for conversion of arithmetic types like int64 y = int64{1} << 42. Do not use C-style cast like int y = (int)x
or int y = int(x)
.
float x = 2.2;
double y = double{x}; // Ok
float z = float{y}; // Ok, but error: conversion from 'type_1' to 'type_2' requires a narrowing conversion
int g = static_cast<int>(y); // Ok
- Use brace initialization to convert arithmetic types (e.g. int64{x}). This is the safest approach because code will not compile if conversion can result in information loss. The syntax is also concise.
- Use static_cast as the equivalent of a C-style cast that does value conversion, when you need to explicitly up-cast a pointer from a class to its superclass, or when you need to explicitly cast a pointer from a superclass to a subclass. In this last case, you must be sure your object is actually an instance of the subclass.
- Prefer the brace initialization to in initialize objects and values of unknown type in generic code;
template <typename T>
void func()
{
T value = T{};
}
Use preincrementation (++i) whenever possible. The semantics of postincrement (i++) include making a copy of the value being incremented, returning it, and then preincrementing the “work value”. Use prefix form (++i) of the increment and decrement operators with iterators and other template objects.
Use const whenever it makes sense. With C++11, constexpr is a better choice for some uses of const. Prefer enums to define constants over "static const int" or "define". Declared variables and parameters can be preceded by the keyword const to indicate the variables are not changed (e.g., const int foo). Class functions can have the const qualifier to indicate the function does not change the state of the class member variables (e.g., class Foo { int Bar(char c) const; };). Prefer "const int* foo" to "int const *foo" (same result). In C++11, use constexpr to define true constants (fixed at compilation/link time) or to ensure constant initialization.
// Good:
const int* p; // pointer to const int
int* const p; // const pointer to int
const int* const p; // const pointer to const int
// Bad:
int const *p; // Perfer "const int *p;"
Every source and header file must contain a license and copyright statement at the beginning of the file.
Prefer using
over typedef
. The using syntax has an advantage when used within templates.
When defining a public alias, document the intent of the new name, including whether it is guaranteed to always be the same as the type it's currently aliased to, or whether a more limited compatibility is intended.
template <typename T> using my_type = whatever<T>;
Local convenience aliases are allowed in function definitions, private sections of classes, explicitly marked internal namespaces, and in .cpp files.
Use type aliases in classes where possible if its helps readability and maintenance:
class Student
{
public:
using Teachers = std::vector<Teacher*>; // Good
const Teachers& getTeachers();
private:
Teachers m_teachers;
};
using
makes it easier to read and makes future possible modifications to what is a collection of Teachers easier (for instance, changing std::vector<>
to std::list<>
)
- RAII guarantees that the resource is available to any function that may access the object (resource availability is a class invariant).
- RAII guarantees that all resources are released when the lifetime of their controlling object ends, in reverse order of acquisition.
- Never use malloc and free. Use C++'s smart pointers (preferably) or the
new
operator.
Functions defined in a header must be marked inline, otherwise every translation unit which includes the header will contain a definition of the function, which will fail to link due to the One Definition Rule (ODR).
Use lowerCamelCase for variable names. Use lowerCamelCase_UpperCamelCase for variable names containing "_".
Avoid short or meaningless names (e.g. "a", "rbarr", "nughdeget") and making abbreviations. Single character variable names are only okay for counters and temporaries, where the purpose of the variable is obvious.
int integralValue;
// private class members:
Timepoint m_StartMark;
bool mb_Paused;
const bool mcb_Debug;
Type names start with a capital letter and have a capital letter for each new word, with no underscores: MyExcitingClass, MyExcitingEnum.
exa::ChronoTimer fpsTimer;
Use CamelCase for source file names and lowerCamelCase for directory names.
For "Writer" class with use as: Writer
class Tool; // Bad: Doesn't describe anything. A tool which does what?
class AbstractTool; // Good
class ToolInterface; // Good
class DebugTool; // Good
Prefer composition over inheritance as it is more malleable / easy to modify later, but do not use a compose-always approach. When using inheritance, make it public.
Inheritance:
class Manager : Person, Employee { // The Manager object is inherited from Employee and Person.
...
}
Composition:
Class Manager { // The Manager object is composed as an Employee and a Person.
private m_Title;
private m_Employee;
...
public Manager(Person p, Employee e)
{
m_Title = e.Title;
m_Employee = e;
...
}
}
Use multiple inheritance only when at most one of the base classes has an implementation and all other base classes must be pure interface classes tagged with the Interface suffix.
Data members of classes, both static and non-static, are named like ordinary nonmember variables, but with a leading "m_".
private:
/** The timepoint stored when the timer was paused last time **/
timepoint m_PausedMark;
/** Is timer running **/
bool m_Running;
/** Is timer paused **/
bool m_Paused;
Use lowerCamelCase
/** Set duration elapsed when the timer was running and not paused **/
void setTotalRunning(const Duration& duration);
Use the override
specifier on all derived from a base class overriding functions.
You may use final
keyword when overriding a virtual
method and requiring that no further subclasses can override it.
Don't annotate a method with more than one of the virtual
, override
, or final
keywords.
Enumerators (for both scoped and unscoped enums) should be named like classes.
enum AlternateUrlTableErrors {
OK = 0,
OUT_OF_MEMORY = 1,
MALFORMED_INPUT = 2,
};
Prefix is allowed, but prefer enum class
instead:
// Good, use as Color::RED etc
enum class Color {
RED,
GREEN,
BLUE
};
// Allowed
enum Color {
COLOR_RED,
COLOR_GREEN,
COLOR_BLUE
};
Use either the //, /** **/ or /* */
syntax, as long as you are consistent.
Code that is not used (commented out) and not part of an explanation shall be deleted.
You can use deprecated on separate line:
[[deprecated]]
void foo() {
// ...
}
In all cases, prefer tabs to spaces in source files. People have different preferred indentation levels, and different styles of indentation that they like; this is fine. What isn’t fine is that different editors/viewers expand tabs out to different tab stops. This can cause your code to look completely unreadable, and it is not worth dealing with.
- Do not use any spaces inside parentheses
- If and else keywords belong on separate lines
- Always prefer braces
if (condition) { // Good - proper space after IF and before {
} else { // Spaces around else
}
- Pad parenthesized expressions with spaces
// Good:
if (x) {
}
x = (y * 0.5f + (y * 0.5f));
// Bad:
if ( x ) {
}
x = ( y * 0.5f + (y * 0.5f )); // Note padding for inner parentheses
- Proper space after Loop/Switch and before
- Don’t use default labels in fully covered switches over enumerations.
- Not fully covered switch statements should always have a default case. If the default case should never execute, simply
assert
:
switch (var) {
case 0: {
...
break;
}
case 1: {
...
break;
}
default: {
assert(false);
// or assert(!"This code should never execute");
break;
}
}
Every case must have a break (or return) statement at the end or a comment to indicate that there's intentionally no break, unless another case follows immediately.
- Always prefer braces
Prefer no spaces inside parentheses in loops.
for (int i = 0; i < 5; ++i) {
for (auto x : counts) {
Use space preceding:
x = *p;
p = &x;
x = r.y;
x = r->y;
char *c;
const string &str;
Always define pointer variables on new line:
int *pp, *pt; // Bad (and worse is int *pp, pt)
Good:
int *pp;
int *pt;
Don't use else after return. So use:
if (foo) {
return 1;
}
return 2;
instead of:
if (foo) {
return 1;
} else {
return 2;
}
The hash mark that starts a preprocessor directive should always be at the beginning of the line. Even when preprocessor directives are within the body of indented code, the directives should start at the beginning of the line!
// Good - directives at beginning of line
if (lopsided_score) {
#if THAT_IS_NICE // Correct -- Starts at beginning of line
DropEverything();
#endif
BackToNormal();
}
// Bad - indented directives
if (lopsided_score) {
#if DISASTER_PENDING // Wrong! The "#if" should be at beginning of line
DropEverything();
#endif // Wrong! Do not indent "#endif"
BackToNormal();
}
- The
public:
,protected:
, andprivate:
keywords should not be indented. - Except for the first instance, these keywords should be preceded by a blank line.
- Do not leave a blank line after these keywords.
- The public section should be first, followed by the protected and finally the private section (methods first, then members, see Declaration Order).
class ChronoTimer : public ATimer<ticks> {
public:
ChronoTimer();
~ChronoTimer();
/** Start timer execution **/
void start() override;
protected:
/** Recalculate timer durations and states **/
void update();
private:
/** The duration elapsed when the timer was running and not paused **/
duration m_TotalRunning;
};
The contents of namespaces are not indented. Namespaces do not add an extra level of indentation.
namespace foo {
namespace bar {
} // namespace foo
} // namespace bar
Make anonymous namespaces as small as possible, and only use them for class declarations.
Anonymous namespaces are a great language feature that tells the C++ compiler that the contents of the namespace are only visible within the current translation unit, allowing more aggressive optimization and eliminating the possibility of symbol name collisions.
Good:
namespace {
class StringSort {
...
public:
StringSort(...)
bool operator<(const char *RHS) const;
};
} // anonymous namespace
Bad:
namespace {
class StringSort {
...
public:
StringSort(...)
bool operator<(const char *RHS) const;
};
void runHelper() {
...
}
bool StringSort::operator<(const char *RHS) const {
...
}
} // end anonymous namespace
// Assignment operators always have spaces around them.
x = 0;
// No spaces separating unary operators and their arguments.
x = -5;
++x;
// Other binary operators usually have spaces around them
v = w * x + y / z;
// Inner parentheses should have no internal padding.
v = ( w * (x + z) ); // Bad
v = (w * (x + z)); // Good
v = (w * ( x + z )); // Good
g = ( v + ((x + (x + z)) + z) ); // Bad
UpperCamelCase (no underscore separation) for template arguments. Place "template" on separate line. Use padding after "template" (before "<>"). Use the brace initialization to in initialize objects and values of unknown type in generic code;
template <typename T>
void func()
{
T value = T{};
}
Prefer "typename" instead of "class" (the difference is "nothing") where possible.
template <typename T> // Good: Padding after "template"; T is UpperCamelCase; "typename" used instead of "class".
class CompressionUtil // Good: Class name is UpperCamelCase and meaningful.
{ // Good: "{" on new line.
...
};
// No spaces inside the angle brackets
vector<string> x;
y = static_cast<char*>(x);
// Spaces between type and pointer are OK, but be consistent.
vector<char *> x;
set<list<string>> x; // Permitted in C++11 code.
- Do not use unsigned types to mean “this value should never be < 0”. For that, use assertions or run-time checks (as appropriate).
- Use size_t for object and allocation sizes, object counts, array and pointer offsets, vector indexes, and so on. The signed types are incorrect and unsafe for these purposes (e.g. integer overflow behavior for signed types is undefined in the C and C++ standards, while the behavior is defined for unsigned types.) The C++ STL is a guide here: they use size_t and foo::size_type for very good reasons.
- In cases where the exact size of the type matters (e.g. a 32-bit pixel value, a bitmask, or a counter that has to be a particular width), use one of the sized types.
Prefer UpperCamelCase type names.
Prefer lowerCamelCase for parameter name based on its type.
void setTopic(Topic* topic)
void connect(Database* database)
for (int i = 0; i < nTables); i++) {
}
/** Is timer paused **/
bool isPaused() const {
return paused;
}
If "is" not appropriate use has/can/should etc
bool hasLicense();
bool canEvaluate();
bool shouldSort();
- get/set
- add/remove
- create/destroy
- start/stop
- insert/delete
- increment/decrement
- old/new
- begin/end
- first/last
- up/down
- min/max
- next/previous
- old/new
- open/close
- show/hide
- suspend/resume
- etc
int maximumAverage = computeAverage() / currentAverage; // Don't use compAvg(), maxAvg or cntAvg
cmd <-> command cp <-> copy pt <-> point comp <-> compute init <-> initialize max <-> maximum cnt <-> current
Don't use that rule for abbreviations (html, CPU)
Line* line; // Don't use Line* pLine; or Line* linePtr;
- Prefer
std::array<char, 32> buffer = {}
orchar buffer[32] = {}
overchar buffer[32] = {0}
- Always initialize variables
char x, *y, z; // Bad
char x = '\0', *y = nullptr, z = '\0'; // Good
Use English language in source code. All code is ascii only (7-bit characters only).
- Prefer if (bSomething) to if (bSomething == true) for boolean values.
- Prefer if (something) to if (something != 0) for int values.
- Prefer if (myPtr) to myPtr != nullptr.
- Prefer "bool result = condition ? true : false;" to "bool result = condition != false;" for variable declaration (same for int, double etc).
- Prefer (foo == 0) to (0 == foo).
- Prefer comparison using epsilon for floating point types like:
template<class T>
typename std::enable_if<!std::numeric_limits<T>::is_integer, bool>::type
almostEquals(T a, T b)
{
if (!std::isfinite(a) || !std::isfinite(b)) return false;
T maximum = std::max({ T{1.0}, std::fabs(a) , std::fabs(b) });
return std::fabs(a - b) < std::numeric_limits<T>::epsilon() * maximum;
}
Use using
only where its necessary.
If a variable represents time, weight, or some other unit then include the unit in the name so developers can more easily spot problems. For example:
uint32 x;
uint32 y;
Prefer constexpr or const variable to "magic number".
double total = 0.0; // Bad: double total = 0;
double speed = 3.0e8; // Bad: double speed = 3e8;
double total = 0.5; // Bad: double total = .5;
Prefer 1D (one-dimensional approach) to 2D (two-dimensional approach) for 2D array representation. For dense matrices the 1D approach is likely to be faster since it offers better memory locality and less allocation and deallocation overhead. Dynamic-1D consumes less memory than the 2D approach. The latter also requires more allocations.
Two-dimensional approach (Bad):
int **ary = new int*[sizeY]; // Bad
for(int i = 0; i < sizeY; ++i) { // Create
ary[i] = new int[sizeX];
}
// May be used as ary[i][j]
for(int i = 0; i < sizeY; ++i) { // Cleanup
delete[] ary[i];
}
delete[] ary;
// ... You could also declare array as:
auto array = new double[M][N](); // Bad
One-dimensional approach (Good):
int *ary = new int[sizeX*sizeY]; // Good
// May be used as ary[i*sizeY+j]
delete[] ary;
If you want to have a matrix class that's always, say, 4x4, prefer container like std::array<float, 4*4> or std::dynarray:
constexpr int sizeX = 4;
constexpr int sizeY = 4;
// Fill with identity matrix
std::array<float, 4*4> m_array{ 1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0 ,
0, 0, 0, 1 };
// Prints identity matrix
for(size_t i = 0; i < sizeX; ++i) {
for(size_t j = 0; j < sizeY; ++j) {
std::cout << m_array.at(i*sizeY+j) << " ";
}
std::cout << "\n";
}
Format Lambdas Like Blocks Of Code:
std::sort(foo.begin(), foo.end(), [&](Foo a, Foo b) -> bool { // Good
return a.bam < b.bam;
});
std::sort(foo.begin(), foo.end(), [&](Foo a, Foo b) -> bool { return a.bam < b.bam; }); // Bad
Always write parentheses for the parameter list, even if the function does not take parameters.
[]() { doSomething(); } // Good, bad is: [] { doSomething(); }
You have to explicitly specify the return type, if the lambda contains more than a single expression.
- Do not use Static Constructors. Static constructors and destructors (e.g. global variables whose types have a constructor or destructor) should not be added to the code base: ** Order of initialization is undefined between globals in different source files. ** "static initialization order fiasco"
- As a rule of thumb, struct should be kept to structures where all members are declared public.
- Do not use Braced Initializer Lists to Call a Constructor
- #include as Little as Possible
- Use the “assert” macro from to its fullest (assert is good for checking a case during run-time). Use static assert to make assertions at compile time.
inline Value *getOperand(unsigned I) {
assert(I < Operands.size() && "getOperand() out of range!"); // Good, prints message.
return Operands[I];
}
Note: You should never depend on assert to do anything because the code can be compiled with NDEBUG defined and then assert does nothing. Production code is oftentimes compiled with NDEBUG defined to ensure that those assert statements disappear.
- Use #ifdef with platform dependent code
- Don’t evaluate end() every time through a loop.
BasicBlock *BB = ...
for (BasicBlock::iterator I = BB->begin(); I != BB->end(); ++I) { // Bad
... use I ...
}
BasicBlock *BB = ...
for (BasicBlock::iterator I = BB->begin(), E = BB->end(); I != E; ++I) { // Good, added E = BB->end()
... use I ...
}
It may be less efficient than evaluating it at the start of the loop. Also if you write the loop in the second form, it is immediately obvious without even looking at the body of the loop that the container isn’t being modified.
- Avoid #include , because many common implementations transparently inject a static constructor into every translation unit that includes it.
- Avoid std::endl because most of the time, you probably have no reason to flush the output stream, so it’s better to use a literal '\n'.
- Spaces Before Parentheses in control flow statements, but not in normal function call expressions and function-like macros.
if (X) ... // Good, bad is: if(X)
for (I = 0; I != 100; ++I) ... // Good, bad is: for(I = 0; I != 100; ++I) ...
while (condition) ... // Good, bad is: while(condition) ...
somefunc(42); // Good, bad is: somefunc (42);
assert(3 != 4 && "laws of math are failing me"); // Good, bad is: assert (3 != 4 && "laws of math are failing me");
A = foo(42, 92) + bar(X); // Good, bad is: A = foo (42, 92) + bar (X);
- Use UTF-8 file encodings.
- Avoid putting project’s header files into the precompiled header file. Put only headers that’ll not change in your precompiled headers – like windows.h, STL headers and header only implementations like rapid json..
- Use LF line endings: Unix-style linebreaks ('\n'), not Windows-style ('\r\n').
- Be careful about your accessors. Always provide the const version, the non-const version is optional. Return by value should be avoided as you will copy the content of your object.
inline int count() const { return count_; } // Ok, int is small type
inline MyType getData() { return m_data; } // Bad, m_data is big struct
inline MyType const & MyClass::getMyType() const { return mMyType; } // Ok, used reference
- Do place spaces around binary and ternary operators.
y = m * x + b; // Good, bad is: y=m*x+b;
f(a, b); // Good, bad is: f(a,b);
c = a | b; // Good, bad is: c = a|b;
return condition ? 1 : 0; // Good, bad is: return condition ? 1:0;
- Place spaces around the colon in a range-based for loop.
for (auto& plugin : plugins) // Good, bad is: for (auto& plugin: plugins)
- Each statement should get its own line, so do not put multiple statements on one line. Good:
x++;
y++;
if (condition)
doIt();
Bad:
x++; y++;
if (condition) doIt();
- An else statement should go on the same line as a preceding close brace if one is present, else it should line up with the if statement. Good:
if (condition) {
...
} else {
...
}
Bad:
if (condition) {
...
}
else {
...
}
- An else if statement should be written as an if statement when the prior if concludes with a return statement. Good:
if (condition) {
return someValue;
}
if (condition) {
return someOtherValue;
}
Bad:
if (condition) {
return someValue;
} else if (condition) {
...
}
- Function definitions: place each brace on its own line. Other braces: place the open brace on the line preceding the code block; place the close brace on its own line. Good:
int main()
{
...
}
class MyClass {
...
};
namespace webcore {
...
}
for (int i = 0; i < 10; ++i) {
...
}
Bad:
int main() {
...
}
class MyClass
{
...
};
- One-line control clauses should use braces (simplifies further editing and prevents bugs).
if (condition) {
doIt();
}
- Prefer Using Directive (using std::vector;) over Using Declaration (using namespace std;).
- Prefer variable names in function declarations. Remove only meaningless variable names out of function declarations if its really necessary.
void setCount(size_t count); // Good, bad is: void setCount(size_t count);
void doSomething(ScriptExecutionContext* context); // Good, bad is: void doSomething(ScriptExecutionContext* context);
- Avoid “using” statements in namespace (or global) scope of header files.
- Singleton pattern Use a static member function named “instance()” to access the instance of the singleton.
class MySingleton {
public:
static MySingleton& instance();
- Use parentheses to group expressions.
- Avoid 64-bit enum values. Not all compilers support them. enum class Enum2 : __int64 {Val1, Val2, val3}; // Avoid
- The binary operators = (assignment), [] (array subscription), -> (member access), as well as the n-ary () (function call) operator, must always be implemented as member functions, because the syntax of the language requires them to. Other operators can be implemented either as members or as non-members.For all operators where you have to choose to either implement them as a member function or a non-member function, use the following rules of thumb to decide where possible:
- If it is a unary operator, implement it as a member function.
- If a binary operator treats both operands equally (it leaves them unchanged), implement this operator as a non-member function.
- If a binary operator does not treat both of its operands equally (usually it will change its left operand), it might be useful to make it a member function of its left operand’s type, if it has to access the operand's private parts. Also don't forget about "self assignment":
MyClass& MyClass::operator=(const MyClass& other) // copy assignment operator
{
if (this != &other) // <-- self assignment check
{
// copy some stuff
}
return *this;
}
- Don't shadow variables
- avoid things like this->x = x;
- don't give variables the same name as functions declared in your class
- Always check whether a preprocessor variable is defined before probing its value
#if Foo == 0 // Bad
#if defined(Foo) && (Foo == 0) // Good
- Avoid varargs. Especially inline variadic functions. Use containers like std::initializer_list instead.
template<typename T, typename... Args>
void func(T t, Args... args) // Avoid, its recursive variadic function.
{
std::cout << t <<std::endl ;
func(args...) ;
}
- Write only one statement per line.
- Always declare a copy constructor and assignment operator Many classes shouldn't be copied or assigned. If you're writing one of these, the way to enforce your policy is to declare a deleted copy constructor as private and not supply a definition. While you're at it, do the same for the assignment operator used for assignment of objects of the same class:
class Foo {
...
private:
Foo(const Foo& x) = delete;
Foo& operator=(const Foo& x) = delete;
};
Any code that implicitly calls the copy constructor will hit a compile-time error. That way nothing happens in the dark. When a user's code won't compile, they'll see that they were passing by value, when they meant to pass by reference (oops).
- Don't use identifiers that start with an underscore (according to the C++ Standard):
- Each name that contains a double underscore (__) or begins with an underscore followed by an uppercase letter (2.11) is reserved to the implementation for any use.
- Each name that begins with an underscore is reserved to the implementation for use as a name in the global namespace.
- Don't write an #include inside an #ifdef if you could instead put it outside.
- Every .cpp source file should have a unique name: Avoid generic names like Module.cpp and instead use PlacesModule.cpp.
- Avoid declaring a class data member with explicit size, in bits (Bit field).
struct { // Following struct might have a size of 8 bytes, even though it would fit in 1:
char ch : 1;
int i : 1;
};
- Prefer enum classes as type-safe bitmasks, see example here
- Never place constants in header files like:
- const int foo = 12; // Bad, inclues on every cpp file
- #define foo 12 // Bad, macros UInstead use "constexpr" or "extern const".
Function overloading should be avoided in most cases. For example, instead of:
// Good:
const Anim* GetAnimByIndex( int index ) const;
const Anim* GetAnimByName( const char *name ) const;
const Anim* GetRandomAnim( float randomDiversity ) const;
// Bad:
const Anim* GetAnim( int index ) const;
const Anim* GetAnim( const char *name ) const;
const Anim* GetAnim( float randomDiversity ) const;
Explicitly named functions tend to be less prone to programmer error and inadvertent calls to functions due to wrong data types being passed in as arguments. Example:
Anim = GetAnim( 0 );
This could be meant as a call to get a random animation, but the compiler would interpret it as a call to get one by index.
- Prefer Use C++ includes, not C includes where possible.
#include <cstdio> // Instead of #include <stdio.h>
#include <cstring> // Instead of #include <string.h>
- Prefer to "library.h" for STL and library headers.
- Prefer unique_ptr instead of shared_ptr where possible. Unique pointers are way more lightweight than shared one.
- For COM objects like Direct3D objects, you should use the smartpointer like "Microsoft::WRL::ComPtr" instead of std smartpointers like "std::unique_ptr".
- Use smartpointers judiciously, prefer raw pointers over smartpointers in most cases. Remember that passing variables by reference was enough in most places. Non-library code should, however, generally prefer smart pointers over raw.
- Avoid std::auto_ptr (its copy semantic is screwed).
- Smartpointer overhead: std::unique_ptr (cannot be copied, can be moved) / std::scoped_ptr (cannot be copied or moved): Has memory overhead only if you provide it with some non-trivial deleter. Has time overhead only during constructor (if it has to copy the provided deleter) and during destructor (to destroy the owned object). std::shared_ptr (can be copied): Has memory overhead for reference counter, though it is very small. Has time overhead in constructor (to create the reference counter), in destructor (to decrement the reference counter and possibly destroy the object) and in assignment operator (to increment the reference counter).
- The C++ Programming Language, 4th Edition by Bjarne Stroustrup (2013).
- Effective Modern C++ by Scott Meyers (2014).
- Large-Scale C++ Software Design by John Lakos (1996).
- C++ Coding Standards: 101 Rules, Guidelines, and Best Practices by Herb Sutter and Andrei Alexandrescu (2004).
- C++ FQA Lite: C++ frequently questioned answers
- Definitions:
- 4.0.0 is a major release, 4.1.0 is a minor release, 4.1.1 is a patch release
- Backward ↓ binary compatibility: Code linked to an earlier version of the library keeps working
- Forward ↑ binary compatibility: Code linked to a newer version of the library works with an older library
- Source code compatibility: Code compiles without modification
- Keep backward ↓ binary compatibility + backward ↓ source code compatibility in minor releases
- Keep backward and forward binary compatibility + forward and backward source code compatibility in patch releases
- Don't add/remove any public API (e.g. global functions, public/protected/private methods)
- Don't reimplement methods (not even inlines, nor protected/private methods)
- Info on binary compatibility: https://community.kde.org/Policies/Binary_Compatibility_Issues_With_C++
- Argument naming
- const bool mcb_Running or const bool m_cbRunning
- Project types like UINT32, INT8 etc
- parentheses padding
- forward declarations
- include guard path
- inlining member functions
- align variable labels
- extern C
- remove c_ for const
- place boolean "b" before name m_bVar
- precompiled headers
- VS directory or filters