%C++ Coding Rules
Lovely rules for happy developers
Version: xxxxxxx
%olibre@Lmap.org
%date
This document is written by developers for developers.
Here "We" means "We the delevopers" (Nous, les développeurs).
We all hate coding rules, often written by poor programmers with weak experience with C++.
Coding rules become outdated, may not be understood by their users, and do not always provide guidance.
But a minimum of rules are required for source code consistency accross our projects.
Therefore, this document aims to provide lovely rules for happy devs:
- Minimum set of flexible rules for source code consistency (Files and Naming)
- Minimum set of useful coding guidelines (Coding)
- Links to other C++ rules/guidelines (Extra)
- Generated source code from third-party frameworks (e.g. Qt-Creator, SBE...)
- Source code mixed with above generated code (i.e. consistency is more important)
- This coding rules apply to code intended for production.
- Source code of tests can follow different rules (e.g. unit-tests).
We prefer an helpful guide rather than a list of static outdated restrictions. Please help maintening this document: suggest/add/remove/clarify/simplify rules.
Choose the way you want to contribute:
- Fork this project, edit the corresponding Markdown file and request a merge.
- Annotate this Markdown file.
- Open an issue).
lower_case
= minuscule[a-z]
and digit[0-9]
(and underscore'_'
to delimite words)UPPER_CASE
= majuscule[A-Z]
and digit[0-9]
(and underscore'_'
to delimite words)camelCase
= start with a lowercase word and capitalize next wordsPascalCase
= capitalize and concatenate words
- Same name as the main class within the file.
- Extensions are
.hpp
and.cpp
only. - No
.h
or.cc
or.cxx
or.inl
...
class MyNewClass -> MyNewClass.hpp // C++ header
MyNewClass.cpp // C++ implementation
// Good
MsgProcessorV4.cpp
ISession.hpp
ISession.cpp
MyNewClass.cpp
// Bad
MsgProcessorV4.cc // C++ source must be '*.cpp'
ISession.h // C++ header must be '*.hpp'
isession.cpp // Must be same as the class name
My_New_Class.cpp // Must be same as the class name
- Concerns directory and file names
- Except
Makefile
,CMakeLists.txt
, C++ files, ...
// Good
foo/bar_configuration.xml
// Bad
Foo/barconfiguration.xml
Most of recent libraries use the following convention:
projectname
├── include
│ └── projectname
│ └── PublicHeader.hpp
└── src
├── CompilationUnit.cpp
└── PrivateHeader.hpp
To include public headers of the library projectname:
#include <projectname/PublicHeader.hpp>
Compilation flag:
gcc app.cpp -I projectname/include
Bad source code organization because does permit #include <projectname/example.hpp>
:
projectname
├── include
│ └── PublicHeader.hpp ## BAD: sub-directory projectname is missing
└── src
├── CompilationUnit.cpp
└── PrivateHeader.hpp
Accepted source code organization in order to simplify file tree depth:
projectname
├── projectname ## GOOD: sub-directory projectname is present
│ └── PublicHeader.hpp
└── src
├── CompilationUnit.cpp
└── PrivateHeader.hpp
gcc app.cpp -I projectname
Bad source code organization because headers and source files are separated too much:
├── cmake
├── doc
├── include Public headers
│ ├── config
│ ├── log
│ ├── monitor
│ ├── persistence
│ └── utilities
├── src Private headers and compilation units
│ ├── config
│ ├── log
│ ├── monitor
│ ├── persistence
│ └── utilities
└── test
├── config
├── log
├── monitor
├── persistence
└── utilities
Accepted source code organization to keep together corresponing *.hpp and *.cpp:
├── cmake
├── doc
└── src
├── config Public headers and compilation units
│ ├── private Private headers
│ └── test
├── log
│ ├── private
│ └── test
├── monitor
│ └── test
├── persistence
│ └── test
└── utilities
├── private
└── test
CMake can help managing dependencies and public headers location because CMake spreads compilation directives from dependees to dependers.
Therefore teams can decide and change the file tree organisation indivually for their sub-module without impacting the dependent teams.
Following *.cmake
example defines the include directory (other target properties are also defined)
file (GLOB hpp_files ${CMAKE_CURRENT_LIST_DIR}/*.hpp)
file (GLOB cpp_files ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
source_group ("SubProjectName Headers" FILES ${hpp_files})
add_library (SubProjectName ${cpp_files} ${hpp_files})
target_include_directories (SubProjectName PUBLIC ${CMAKE_CURRENT_LIST_DIR}/..) # Location of public headers
target_link_libraries (SubProjectName PUBLIC config monitor log)
include (${CMAKE_CURRENT_LIST_DIR}/test/test.cmake)
Following *.cmake
example disables dependee header warnings (compiler does not print dependee header warning when compiling depender)
add_library (gtest ${CMAKE_CURRENT_LIST_DIR}/googletest/src/gtest-all.cc)
# keyword SYSTEM disables compilation warning (e.g. keyword SYSTEM replaces -I by -isystem)
target_include_directories (gtest SYSTEM PUBLIC ${CMAKE_CURRENT_LIST_DIR}/googletest/include)
target_include_directories (gtest PRIVATE ${CMAKE_CURRENT_LIST_DIR}/googletest)
find_package (Threads)
target_link_libraries (gtest Threads::Threads)
if (CMAKE_COMPILER_IS_GNUC OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
target_compile_options (gtest PRIVATE -w) # Flag -w disables compilation warning
endif()
- Double quotes
"xxxx"
for header in the same directory - Double quotes
"xxxx"
for header of same module (optional) - Angle brackets
<xxxx>
(chevrons) for external libraries (from another project) - Both are OK to include header from another module but within the same project
This example uses:
-
Angle brackets
#include <AAA/a.h>
for header from another module -
Double quotes
#include "BBB/a.h"
for header of the same module -
Double quotes
#include "b.h"
for local headerAA └── include └── AA BB └── a.h ├── src | #include <AA/a.h> │ ├── a.cc -------| #include "BB/a.h" │ └── b.h └── include └── BB ├── a.h ----| #include "b.h" └── b.h
a.cc ├── includes AA/include/AA/a.h └── includes BB/include/BB/a.h └─ includes BB/include/BB/b.h (does not include BB/src/b.h)
#include "MyHeader.hpp" // "" when in the same directory as the current C++ file
#include "private/PrivateHeader.hpp" // "" in a sub-directories of the current C++ file
#include "xxxx/PublicHeader.hpp" // "" public header of same module (if in a different directory)
#include "other_module/Header.hpp" // You can choose "" or <> to include
#include <other_module/Header.hpp> // from another module of same project
#include <3rdparty/xxx/External.hpp> // <> for third parties
#include <iostream> // <> for system headers
The sequence of #include
is not important as long as the project compiles using any order.
A simple check is to include first the headers of the current sub-module, then libraries, third-parties and finally the system headers (as in the above example).
Moreover the unit test compilation unit first includes the header to be tested:
/// File MyClass_spec.cpp in order to test the class MyClass
#include "myproject/MyClass.hpp" // This checks that MyClass.hpp includes all its dependencies
#include "MyClass_spec.hpp"
#include <gtest/gtest.h>
#include <string>
The following bash script replaces #include <dir/header.h>
by #include "header.h"
when both files (header and includer) are in the same directory:
find -name "*.h*" -printf '%f\n' |
while read h
do
fgrep "/$h" -RI | fgrep -w 'include' | tr -d '\r' |
awk -F'[:# ]*include *[<"]' '{print $1 "\t" $2}' |
while read f g
do
[[ -f "${f%/*}/$h" ]] &&
sed "s|.$g|\"$h\"|" -i "$f"
done
done
The above script may mix header having same filename. Therefore this is another script to control the result of the previous script when headers have same filename:
find -type f -printf '%f\n' |
sort | uniq -d |
while read f
do
echo ________________________________
find -name "$f"
git diff | tac | sed -n "/$f/,/^---/p" | tac
done
Or this alternative if you like diff-hilighting of your editor:
find -type f -printf '%f\n' |
sort | uniq -d |
while read f
do
git diff | tac | sed -n "/$f/,/^---/p" | tac | tee out
[[ -s out ]] && echo --------------- &&
find -name "$f" &&
echo _________________________________
done | tr -d '\r' | vim -
(tiny command line tac | sed -n "/$f/,/^---/p" | tac
is from Sundeep at question Print the smallest set of lines between two patterns)
-
All header files contain its own Header-guard
-
Use
#ifndef
/#define
/#endif
-
No need
pragma once
(See StackOverflow) -
Format is
<DIRECTORY>_<FILE_NAME>_HPP_
to guaranty uniqueness -
Underscores
'_'
can be inserted in<FILE_NAME>
to separate words -
Automatic script can be used to append/fix header-guards
-
As reminder the macro name has some constraints. Extract from cppreference.com/Identifiers:
- the identifiers with a double underscore anywhere are reserved
- the identifiers that begin with an underscore followed by an uppercase letter are reserved
Example for file: foo/bar/ISessionV4.hpp
#ifndef FOO_BAR_ISESSION_V4_HPP_
#define FOO_BAR_ISESSION_V4_HPP_
// ...
// ...
// ...
#endif // end of header-guard
- Copyright every ASCII file (not only C++ files)
- Automatic script can be used to append/fix copyright banners
- Be inspired by following banner based on Doxygen tags
- Keep the Doxygen tag
\file
empty to use the name of the current (prevent misspeling if filename is changed)
/**
* \copyright Copyright (C) 2015-2016 MyCompany
* All Rights Reserved
* DO NOT ALTER OR REMOVE THIS COPYRIGHT NOTICE
*
* \file
* \brief <description of file purpose>
* ...
*/
- Doxygen comment at the top of every C++ file (brief description and copyright).
- Doxygen comment for class and function in the header file (see doxygen.org).
- No duplication of Doxygen comments in the
*.cpp
file (i.e. the good place is the header file)
/**
* Header file for class MyMessage
*
* \brief The header for the MyMessage class
*
*/
- Write
FIXME
/TODO
/TOREVIEW
in source code when neccessary - Format
"TODO (name@example.com): reason"
- Follow/Complete them (in an acceptable time)
- A deadline can be provided
Deadline DATE
Label | Jenkins priority | Meaning |
---|---|---|
FIXME |
High | When the hack or hardcoded constant [or ...] is critical and should be removed before the next release. Usually FIXME is used to do a fast prototyping. |
TODO |
Normal | Explain some improvments or when you have to do a (usually complex) change, or you are too lazy to do it in that particular moment. If it is something critical, and the software could not be released without that code or fix, then use a FIXME . On the other hand, if the change will not affect the proper behaviour of the program, and you are comfortable in leaving the code as it is, then use TODO . |
TOREVIEW |
Low | "please review this code with special attention", regardless of the usual review process. |
Rationale:
- It is important to note where work needs to be done versus work that was forgotten.
- It is equally important to note what work needs to be done, along with the contact information for the developer that is responsible for the work.
- Ideally a limit date of completion should be provided for follow-ups.
// Formatting
blabla; // TODO (YourName@example.com): What needs to be done
blabla; // TODO: Deadline DATE
// Good
blabla; // TODO (JohnDoe@example.com): Update function to provide concatenation operator
blabla; // TODO: Deadline November 2012.
/**
* TODO (JohnDoe@example.com): Remove 32-bits ID once all clients have migrated to 64-bit ID
*/
// Bad
blabla; // TODO
blabla; // todo: refactor blabla
- Indentation is 4 spaces because this is the most adopted convention.
- No tabulation.
- This is the default settings for many editors/IDE.
- Recent C++ projects often use 4 spaces identation.
booost::hana
- LLVM STL implementation
- Microsoft GSL implementation
- Allman is the formatting style of source code.
- Styles Kernighan & Ritchie and 1TBS are accepted for tiny blocks (a couple of lines).
- Script to automate Allman-formating can be used (e.g. commit hook).
Full Allman style using 4 spaces indentation
#include <iostream>
int main (int argc, char *argv[])
{
if (argc > 1)
{
std::cout << argv[1] << std::e ndl;
}
for (int i = 0; i < 9; ++i)
{
if (i == 5)
continue;
if (i % 2)
{
std::cout << i << std::endl;
}
if (i > 5)
{
std::cout << 9 - i << std::endl;
}
else
{
std::cout << 2 + i << std::endl;
}
}
return 0;
}
Same code but using style 1TBS for short blocks
#include <iostream>
int main (int argc, char *argv[])
{
// Good: Allman style at large scope
if (argc > 1)
{
std::cout << argv[1] << std::endl;
}
// Good: Allman style for long block
for (int i = 0; i < 9; ++i)
{
if (i == 5) continue; // One line accepted (but not nice to debug)
// 1TBS accepted for tiny block
if (i % 2) {
std::cout << i << std::endl;
}
// 1TBS also accepted here but block should stay tiny
if (i > 5) {
std::cout << 9 - i << std::endl;
} else {
std::cout << 2 + i << std::endl;
}
}
return 0;
}
The 80 column rule is still common in coding standards today (for example: Google's Java and Linux kernel standards).
Do not worry, there is no such limit in our coding rules.
However keeping lines short is nice:
- Code written using short lines is often easier to read/understand
- But alignment is often more important than pseudo-cosmetic rules
- Lines necessary short when printed for code review (prevent ugly wrapping)
- 3-way diff/merge is difficult when lines are too wide
When some devs leave trailing spaces and others remove them, the repository becomes to be polluted by these unrelevant changes. In order to avoid this noise, we can remove trailing whitespaces before/during commit.
- No space or tabs at the line end for every ASCII file
(except some special meaning as for hard-break-line in Markdown) - Automatic Git hook may fix or reject commit having trailing whitespaces
- Most of IDEs can be configured to remove trailing whitespaces on save
- Emacs:
show-trailing-spaces
anddelete-trailing-spaces
- Emacs:
- Command lines (can be used to atomatically fix source code)
grep '\s\+$' --color -lR src_dir
to list bad filesGREP_COLORS='mt=;41' grep '\s\+$' --color -n9 file.cpp
to hightlight trailing spacessed 's/[ \t]*$//' -i file.cpp
to remove trailing whitespaces
When some devs work on MS-Windows and others on Unix platforms, ASCII files may contain both End Of Line:
- Good: BSD/GNU/Linux/Mac/... use Line Feed
"\n"
(LF) - Bad: MS-Windows use by default CR+LF
"\r\n"
Rules for every ASCII file:
- Use only Line Feed
"\n"
(LF) - No Carriage Return
"\r"
(CR) - Automatic Git hook may fix or reject commit containing CR
"\r"
- Configure your IDE to use Line Feed
"\n"
(LF) only - Command lines (can be used to atomatically fix source code)
grep '\r' --color -lR src_dir
to list bad filesGREP_COLORS='mt=;41' grep '\r' --color -n9 file.cpp
to display bad linesdos2unix file.cpp
to convert CR+LF to LFtr -d '\r' <file.cpp >file.new && mv file.new file.cpp
ifdos2unix
is not availabletr -d '\r' <file.cpp | sponge file.cpp
if youyum install moreutils
When a file is cut (end of file is missing), a simple trick is to check
In order to detect a potential file corruption (i.e. when the end of the file is missing), we usually check the last character of the file. File can be considered as OK when its last character is a newline.
Moreover final blank lines are not necessary and some devs may clean them produising some noise within the commit diff.
Rules:
- Every ASCII-based file ends with a Line Feed
"\n"
(LF) - No unnecessary final blank lines
- Automatic Git hook may fix or reject commit missing final
"\n"
- Most of IDEs already take care of the final newline character and my warn if missing
- Command lines (can be used to atomatically fix source code)
echo "$(< file.cpp)" > file.new && mv file.new file.cpp
to clean unecessary final blank lines and append a final newline if missingecho "$(< file.cpp)" | sponge file.cpp
if youyum install moreutils
See also Clang compiler option -Wnewline-eof
.
No Hungarian notation as modern IDEs display underlying types.
Main idea
Notation | Meaning |
---|---|
UPPER_CASE |
Macros and Constants |
PascalCase and lower_case_type |
Types and nested types |
camelCase and lower_case |
Functions and Variables |
- Prefer C++ features (i.e.
template
functions) - Avoid macros (pre-processor) when possible
// Good
#define FIND_ROOT(x)
#define PI_ROUNDED 3.14
#ifdef BIG_ENDIAN
// Bad
#define FINDROOT(x)
#define piRounded 3.14
#ifdef BigEndian
enum class
(C++11) values can usePascalCase
notation because the prefixEnumName::
clarify the meaning
Priority:
- Avoid
#define
when possible
- Prefer
enum
when the constants are same type or used in sameswitch case
(see-Wswitch-enum
) - Prefer
constexpr
(C++11) thanconst
- Prefer
const
than anonymousenum
// Bad
#define max_counter 20 // Name must be UPPER_CASE
const int32_t iMaxCounter = 20;
// Correct
#define MAX_COUNTER 20
// Good
enum { MAX_COUNTER = 20 };
const int32_t MAX_COUNTER = 20;
static const int32_t MAX_COUNTER = 20;
// Better
constexpr int32_t MAX_COUNTER = 20;
// unnammed-namespace is better than above 'static const'
namespace
{
const int32_t MAX_COUNTER = 20; // Link is external in C++03 and internal since C++11
static const int32_t MAX_COUNTER = 20; // Link is internal for both
}
- User-defined
class
/struct
/union
/enum
names inPascalCase
- Underscore
'_'
should not be required, but can be accepted - Notation
lower_case_type
(i.e. suffix_type
) can be used for nested types - Suffix
*_t
should not be used because reserved for potential future C++ standard types (see type shadowing) - For declarations having many lines => After the closing brace
}
add an ending comment - Automatic script may detect/fix the closing brace comment
// Good
struct Session;
class FooSession;
class MyNewClass
{
// .. lots of lines
}; // end of MyNewClass
// Bad
struct session;
class foo_session;
class myNewClass;
- Prefix
'I'
for class specifing an interface (i.e. pure abstract class)
(See also corresponding question on StackExchange)
// Good
IReactor;
ISession;
IFooSession;
IMsgProcessor;
// Bad
Ireactor;
isession;
I_Foo_Session;
imsgprocessor;
- Start with a verb when possible
- Underscore
'_'
can be accepted if consistent with the whole (lower_case
) - No redundancy of the class name within the method name
// Good
class Acceptor
{
public:
int32_t initialize();
int32_t getValue();
int32_t findValue();
int32_t setValue();
};
// Bad
class Acceptor
{
public:
int32_t initializeAcceptor(); // Redundancy name of the class
int32_t GetValue(); // First word is not full lowercase
int32_t find_value(); // Can be accepted if consistent with the whole
int32_t setvalue(); // Missing Capital on second word
};
- Underscore
'_'
can be accepted if current scope is consistent (i.e. other functions use same naming) - Avoid warning due to flag
-Wunused
-
comment unused parameter
-
or use a macro
UNUSED(param)
(this macro let Doxygen knowing about the parameter name)#ifdef DOXYGEN_PARSING # define UNUSED(x) x #else # define UNUSED(x) #endif
-
or use macro
DEBUG_ONLY(param)
when used only in debug build#if !defined(NDEBUG) || defined(DOXYGEN_PARSING) # define DEBUG_ONLY(x) x #else # define DEBUG_ONLY(x) #endif
-
/// Divides the quantity by two
int32_t divideByTwo (int32_t qty, bool /*force*/) { return qty/2; }
/// Divides the quantity by two
/// \param[in] force dummy comment
int32_t divideByTwo (int32_t qty, bool UNUSED(force)) { return qty/2; }
/// Divides the quantity by two
int32_t divideByTwo (int32_t qty, ///<[in] quantity
bool DEBUG_ONLY(force)) ///<[in] dummy comment
{
assert(force==true || force==false);
return qty/2;
}
- The suffix
_t
may clarify code but is optional
template<typename Callable, template<typename> typename BigContainer, typename ForwardArray = MyClassArray>
class MyClass
{
Callable _callback;
BigContainer<ForwardArray> _container;
};
- Non-Type template parameters are constants, therefore could be logic to be in
UPPER_CASE
- However
PascalCase
may also be used if it renders better
template<size_t SIZE = 512>
class MyClass
{
int32_t _myArray[SIZE];
};
- As our functions are small, we do not need a naming trick to distinguish function parameters and local variables
- Same
camelCase/lower_case
rule as for function parameter
// Good
Session session1;
MyClass myClass;
// Bad
Session Session1; // Name must start with a lowercase letter
MyClass my_class; // Can be accepted if consistent
- Same as above
camelCase/lower_case
rule - No redundancy of the
struct
name within the member name
struct Limit
{
int32_t quantity; // Good
double price; // Good
double limitPrice; // Bad - Duplicated word 'limit' with struct name
bool isActive;
};
- Same rule as above
struct
member but prefixed with an underscore'_'
- This rule may not be applied (follow instead previous rule (Struct data member)):
- when the class contains few functions and data members
- to
static
members and other special members
Rationale:
- By convention
struct
haspublic
data and provides few functions - In constrast,
class
hasprivate
data and often provides many functions - This rule avoids messing the Auto-Completion on
class
between data (private) and functions (public) - This rule allows Auto-Completion to separate data/functions (arranged in alphabetical order)
- Developer knows if he/she wants to see the list of data memebers or the list of functions
- Prefix by
"_" + lowercase
is less reserved than prefix"_" + UPPERCASE
or prefix"__"
)
class MyClass
{
public:
double getTradingVolume() const;
private:
int32_t _quantity;
double _price;
bool _isActive;
};
double MyClass::getTradingVolume() const
{
return _price * _quantity;
}
See also related questions:
- Why use prefixes on member variables in C++ classes?
- Convention of using underscores in C++ class member names
- Class members prefixed with underscore
- Might starting variables/members with an underscore puzzle the compiler?
- Why C# and C++ use
_name
convention? - What kind of prefix do you use for member variables?
The names of global variables should start with
//
.Ideal way to use a global variable:
// my_global_variable; ^^ // The leading "//" improves this global variableIt’s a joke.
Sort of.
The truth is that there are cases when global variables are less evil than the alternatives — when globals are the lesser of the evils. But they’re still evil. So wash your hands after using them. Twice.
Instead of using a global variable, you should seriously consider if there are ways to limit the variable’s visibility and/or lifetime, and if multiple threads are involved, either limit the visibility to a single thread or do something else to protect the system against race cases.
Note: if you wish to limit the visibility but neither lifetime nor thread safety, two of the many approaches are to either move the variable into a class as a
static
data member or to use an unnamednamespace
.Here is how to move the variable into a class as a
static
data member:class Foo { // ... static int g_xyz// See the Construct Members On First Use Idiom // ... };Here is how to use an unnamed
namespace
:namespace { int g_xyz; // See the Construct On First Use Idiom for non-members }Repeat: there are three major considerations: visibility, lifetime, and thread safety. The above examples address only one of those three considerations.
- Abreviations are welcome to minimize discomfort (e.g.
std::
) - Avoid multi-words when possible
- Do not list nested namespaces on the same line
- No need for indentation
- Add an ending comment after the closing brace
}
(for namespace having many lines) - Automatic script may detect/fix the closing brace comment
// Good
namespace foo {
namespace bar
{
class Limit
{
public:
// functions...
private:
// data...
}; // End class Limit
} // End namespace foo
} // End namespace ct
// Bad
namespace FOO { namespace BAR
{
class Limit
{
public:
// functions...
private:
// data...
};
}
}
// Better: C++17 (already available on recent compiler versions)
namespace foo::bar
{
class Limit
{
public:
// functions...
private:
// data...
}; // End class Limit
} // End namespace foo::bar
- Base name followed by Derived name
- Major name followed by Minor name
- Parent name followed by Child name
// In the following examples, "IMsg" is the Base name
class IMsg;
// Good order "Base -> Derived"
class IMsgProcessor;
class IMsgManager;
class IMsgLogon;
// Bad order "Base -> Derived"
class IProcessorMsg;
class IManagerMsg;
class ILogonMsg;
Give as descriptive a name as possible, within reason. Do not worry about saving horizontal space as it is far more important to make your code immediately understandable by a new reader. Do not use abbreviations that are ambiguous or unfamiliar to readers outside your project, and do not abbreviate by deleting letters within a word.
Our rules:
- Large scope names convey meaning (semantics), so they should be descriptive
- Local scope names only meaningful in their small context (lexical scope), so they may be shorter
- Abbreviation are welcome to shorten namespace names
// Good
int price_count_reader; // No abbreviation
int num_errors; // "num" is a widespread convention for 'numbers of'
int num_dns_connections;// Most people know what "DNS" stands for
// To be reviewed
int n; // May be long enough in small scopes (for-loop, tiny function...)
int nerr; // Ambiguous abbreviation but same remark as for "int n;"
// Bad
int n_comp_conns; // Ambiguous abbreviation
int wgc_connections; // Only your group knows what this stands for
int pc_reader; // Lots of things can be abbreviated "pc"
int cstmr_id; // Deletes internal letters
- All code is written in English (names, comments...).
int price; // Good
int precio; // Bad
int prix; // Bad
int preis; // Bad
OK for safe code, but no performance sacrificed, we are C++ devs!
-
Use explicit-sized types for integers
(No need to use namespacestd::
even if we have to#include <stdint.h>
instead of#include <cstdint>
)int8_t uint8_t int_fast8_t uint_fast8_t int16_t uint16_t int_fast16_t uint_fast16_t int32_t uint32_t int_fast32_t uint_fast32_t int64_t uint64_t int_fast64_t uint_fast64_t
-
Avoid
char
,short
,int
,long
,long long
when possible -
Keep
char
for characters (string) -
Use
uint8_t
for data buffer (based on bytes, not characters) -
Use
size_t
for size -
Use
intptr_t
/uintptr_t
for integers storing a pointer adress (be careful: cannot store a virtual function pointer) -
Use
diffptr_t
for pointer difference -
Almost Always
auto
(C++11)
-
Always initialize variables before use
// Good int32_t quantity = 0; float price = 0.0; char* text = nullptr; // C++11 // Bad int32_t quantity; float price; char* text;
-
Except when we know what we do within a performance-critical section (justification/comment required)
float x; // x is uninitialized bool ok = get(x); // pass x by reference if (ok) // if x is set, get(x) returns true { /* use x */ } // use x only inside this scope (outside x may be unitilized)
-
Local variables are initialized according to their use
for (int32_t i = 10; i >= -10; --i) { }
-
Global variables or variables with a large scope, which do not have a meaningful default value, should be initialized with values indicating an error (e.g., an invalid value), rather than "low-profile" values that facilitate silent bugs.
In enum types, there should be an invalid enum value to be assigned to variables which have no valid default values.int8_t* buffer_ptr = 0xDeadBeef; // a famous invalid pointer value (in the memory range) size_t msg_count = 0; // a meaningful default value at start int64_t event_id = MIN_INT64; // an invalid event id, variable meaningless unless actively used
-
When initializing values to zero/null
- For integers use
0
- For reals use
0.0
- For pointers use
0
orNULL
(C++03) - For pointers use
nullptr
(C++11)
- For integers use
-
C++11: Initialize data members on declaration (not neccessary on constructor)
class Limit { public: Limit() = default; private: int32_t quantity = 0; double price = 0.0; };
-
C++11: Use braces initialization (instead of parentheses)
int32_t quantity(0); // parentheses int32_t quantity{0}; // braces
-
C++17: Use
not_null<T>(ptr)
to convey pointers that should never be null (already available in the GSL)
The following parenthesis initialization ambiguity is known as the Most vexing parse. Check the compilation on gcc.godbolt.org.
#include <stdint.h>
// Parenthesis can also be Bad
float foo (float param)
{ // Function declarartion
float ret( float(param) ); // float ret( float );
return ret;
}
// Use instead braces (C++11)
float bar (float param)
{
float ret{ float(param) }; // No ambiguity
return ret;
}
// C++11 uniform initialization syntax (initializers)
int32_t quantity {0};
float price {0.0};
char* text {nullptr};
Note: Value initialization changed since C++11. Value initialization is when parentheses/braces are empty: T(); T{}; new T(); new T{};
Build/Run below snippet on Coliru
#include <iostream>
struct A
{
A() {} // ctor does not initialize 'i'
int i;
};
struct B // implicit ctor
{
A a;
int i;
void set() { a.i = i = 42; }
};
std::ostream& operator<< (std::ostream& os, const B& b)
{
os <<'\t'<< b.a.i <<'\t'<< b.i;
return os;
}
int main()
{
std::cout <<"----------"<< __cplusplus <<"----------" "\n";
B b; // used to reset memory for 'placement new'
b.set(); std::cout <<"new(&b)B "<< *new(&b)B <<'\n'; // All uninitialized (in all C++ standards)
std::cout <<" B() "<< B() <<'\n'; // B::A::i uninitialized in C++03, zero-initialized in C++11
b.set(); std::cout <<"new(&b)B() "<< *new(&b)B() <<'\n'; // B::i zero-initialized (in all C++ standards)
#if __cplusplus > 2011*100 // B{} is aggregate-initialization (DR1301)
std::cout <<" B{} "<< B{} <<'\n'; // => B::A::i value-initialized
b.set(); std::cout <<"new(&b)B{} "<< *new(&b)B{} <<'\n'; // => B::i zero-initialized
#endif
}
Build output & Possible run output
> clang++ --version
clang version 3.7.0 (tags/RELEASE_370/final 246979)
Target: x86_64-unknown-linux-gnu
Thread model: posix
> clang++ -std=c++03 -Wall -Wextra -pedantic main.cpp && ./a.out
----------199711----------
new(&b)B 42 42
B() 0 0
new(&b)B() 0 0
> clang++ -std=c++11 -Wall -Wextra -pedantic main.cpp && ./a.out
----------201103----------
new(&b)B 42 42
B() 0 0
new(&b)B() 0 0
B{} 4196348 0
new(&b)B{} 42 0
> clang++ -std=c++14 -Wall -Wextra -pedantic main.cpp && ./a.out
----------201402----------
new(&b)B 42 42
B() 0 0
new(&b)B() 0 0
B{} 4196348 0
new(&b)B{} 42 0
> clang++ -std=c++1z -Wall -Wextra -pedantic main.cpp && ./a.out
----------201406----------
new(&b)B 42 42
B() 0 0
new(&b)B() 0 0
B{} 4196348 0
new(&b)B{} 42 0
- Unlimited multiple inheritance for Interface (pure abstract class)
- Private multi-inheritance accepted
- Avoid multiple inheritance for other cases when possible
- Avoid operator overloading when possible
(especially inside class) - Except for:
- Arithmetic types as a Price class using significand/exponent
std::ostream& operator<< (std::ostream&, const YourType&);
- Place
operator<<
outside YourType (and your namespace) - Try to avoid
friend
(all required data should be publicly accessible using functions at least)
- Place
- No C style casts except where consistent with local/legacy code.
- No scalar constructor for conversion.
- Prefer
static_cast
anddynamic_cast
static_cast
is the compile-time type-checked cast (used in place of legacy C style cast)dynamic_cast
is the run-time type-checked cast
- Better fixing the design instead of using
const_cast
const_cast
removes the const-ness
- Avoid
reinterpret_cast
when possiblereinterpret_cast
should be used only forvoid*/intptr_t
conversions (no type-check)- Requires review by skilled devs
int32_t j = static_cast<int32_t>(x); // Good
int32_t j = (int32_t)x; // Bad
int32_t j = int32_t(x); // Do not abuse!
-
In a project, protect your code with a namespace (e.g. from third parties)
-
Public headers in a sub-directory having same name as library name
Example:main.cpp
includessrc/foobar/include/foobar/Message.hpp
main.cpp: #include <foobar/Message.hpp> compile: gcc main.cpp -I src/foobar/include
-
Use namespace (mandatory for shared/static/header library)
-
Keep namespace name short and relevant:
- Encapsulate the library "shmq" within the namespace "shmq" (same name)
- In UTP base code, use "utp"
-
Statement
using ns;
is sometimes used (wherens
is a namespace)
In order to minimize collision:- In a namespace, separate public and hidden stuff (hidden stuff within a sub-namespace)
- Limit "using ns;" in bounded scope (not in header file global scope)
-
Namespace alias can also be used to avoid abusing of
using ns;
e.g.namespace gpi = ::google::protobuf::internal;
#include <iostream>
#include <stdint.h>
namespace n1 {
namespace internal
{
int32_t x = 1;
}
using namespace internal;
int32_t y = 1;
int f1() { return x + y; }
} // namespace n1
namespace n2 {
namespace internal
{
double d = 2.0;
}
using namespace internal;
double y = 2.0;
/* Errors:
double f2() { return internal::x + y; } // 'x' is not a member of 'n2::internal'
double f3() { return x + y; } // 'x' was not declared in this scope
*/
double f4() { return d + y; }
} //namespace n2
int main()
{
using namespace n1;
using namespace n2;
using namespace std;
cout <<"f1() "<< f1() << endl;
cout <<"f4() "<< f4() << endl;
cout <<"n1::y "<< n1::y << endl;
cout <<"n2::y "<< n2::y << endl;
cout <<"n1::internal::x "<< n1::internal::x << endl;
cout <<"n2::internal::d "<< n2::internal::d << endl;
// Errors:
// cout << internal::x << endl; // the same error as in f2
// cout << y << endl; // ambiguous n1 vs n2
}
- Magic numbers should not be used directly in the code
- All constant numbers are defined as
enum
(fine for set of constants)constexpr
(C++11)static const
(C++03)
- Exceptions:
- Integer values 0 and 1
- Array size because the number of elements is retrievable using
const std::size_t sz = (sizeof(array) / sizeof(*array));
const std::size_t sz = std::size(array);
(C++17)
When an exception is thrown the program unwinds the stack (properly destruct the local variables). If during this unwinding process, a destructor throws a second exception then the behaviour is undefined as the language runtime can only propagate one single exception.
- Never throw exceptions in destructor:
- No
throw
in destructor - No throwable functions in destructor
- No
dynamic_cast<>(reference)
in destructor
(throws an exception when cannot cast the reference)
- No
try/catch
does not prevent undefined behaviour (crash) when a second exception occurs
User-defined exceptions:
- inherit
std::exception
- override virtual method
what()
This make catching exception easier.
The override
keyword expresses polymorphism intent and helps catch errors due to refactoring
and design changes early.
When you declare a symbol, either a function, a class or an instance, in a compilation unit, make sure you have good reason not to publish it in a header, then put it inside an unnamed (aka anonymous) namespace to prevent any conflict at link time.
Compiler flag | Comment |
---|---|
-Wall |
Classic warnings |
-Wextra |
Extra amount of warnings |
-Weffc++ |
Effective C++ series of books from Scott Meyers |
-pedantic |
Reject code not following ISO C++ (e.g. GNU extensions) |
-pedantic-errors |
Pedantic warnings are converted into errors |
-Winit-self |
Variables initialized with themselves (enabled by -Wall) |
-Wswitch-enum |
Missing case for values of switch(enum) |
-Wcast-align |
Incompatible alignment pointers |
-Wcast-qual |
Cast between pointers leads to target type qualifier removal |
-Wconversion |
Conversion might lead to value alteration, confusing overload resolution |
-Wformat=2 |
Invalid argument types and format strings in formatting functions (printf, scanf...) |
-Wuninitialized |
Variable used without being initialized |
-Wmissing-field-initializers |
Fields is left uninitialized during (non-designated) structure initialization |
-Wmissing-include-dirs |
User-supplied include directory does not exist |
-Wpointer-arith |
[void and function] Operations addition/subtraction/sizeof are GNU extension |
-Wredundant-decls |
Multiple declarations of the same entity is encountered in the same scope |
-Wshadow |
Variable/typedef/struct/class/enum shadows another one having same name |
-Wswitch |
Missing enumerated type in 'case' labels |
-fstack-protector-strong |
Checks for buffer overflows such as stack smashing attacks (extra code is added) |
-Wstack-protector |
Warn if option '-fstack-protector-strong' complained about codes vulnerable to stack smashing |
-Wunreachable-code |
Unreachable code |
-Wunused |
Unused entity (functions, labels, variables, typedefs, parameters, ...) |
-Wwrite-strings |
Deprecated conversion from string literals to 'char *' (enable by default in C++) |
-fmax-errors=50 |
Limit number of errors to 50. Default is 0 => no limit. |
-Werror=implicit-function-declaration |
Implicit function declarations allows to call functions without declaring them, and therefore is bug-prone because these functions use a different calling convention and have a fixed return type of int. Resulting issues are pointer truncation (on 64-bit architectures), exposure of padding bits (particular for bool-returning functions on x86_64), and unexpected lack of hardening. Implicit function declarations are not part of C++. |
-Werror=implicit-int |
Implicit ints are usually source code bugs, and the presence of such code may interfere with future C language directions |
Some develper use -Werror
to fail compilation for any warning. But when -Werror
is set within a build chain, a new compiler version or a change on third-party may fail the build for any minor warning. Please think also about people building your software.
Clang has a crazy compilation option -Weverything
that enables all possible warnings. The idea is to enable all and disable the annoying ones.
Compiler flag | Comment |
---|---|
-Weverything |
|
-Wno-c++98-compat |
|
-Wno-c++98-compat-pedantic |
|
-Wno-unused-macros |
|
-Wno-newline-eof |
|
-Wno-exit-time-destructors |
|
-Wno-global-constructors |
|
-Wno-gnu-zero-variadic-macro-arguments |
|
-Wno-documentation |
|
-Wno-shadow |
|
-Wno-missing-prototypes |
|
-fsave-optimization-record |
Indicate why some code has not been optimized (see C++FRUG #17) |
See more on GCC warning options.
- Excellent C++ modern guidelines from Bjarne Stroustrup and Herb Sutter
- Popular but criticized Google C++ Style Guide
- Boost Library Requirements and Guidelines
- BDE Coding Standards
- Bjarne Stroustrup's C++ Style and Technique FAQ
- GCC Coding Conventions
- The JSF air vehicle C++ coding standards
The two following lines lines can be inserted to all C++ files:
// -*- mode: C++; coding: utf-8; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4; c-file-style: "linux" -*-
// vi:ft=cpp fenc=utf-8 ts=8 et sw=4 sts=4 tw=80
The first line is for Emacs and the second one for Vim.
See more details about these abreviated Vim settings:
For clang-format v3.8, create file .clang-format
with the following content
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
AlignEscapedNewlinesLeft: true
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: false
BinPackArguments: true
BinPackParameters: false
BreakBeforeBraces: Custom # Use BraceWrapping settings
BraceWrapping:
AfterClass: true
AfterControlStatement: false
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterStruct: true
AfterUnion: true
BeforeCatch: true
BeforeElse: false
IndentBraces: false
BreakBeforeBinaryOperators: false
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: true # Does not work :(
ColumnLimit: 0
CommentPragmas: '^ IWYU pragma:'
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: true
DisableFormat: false
ExperimentalAutoDetectBinPacking: true
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
IncludeCategories:
- Regex: '^".*\.hpp"'
Priority: 1
- Regex: '^".*\.h"'
Priority: 2
- Regex: '^<.*\.hpp>'
Priority: 3
- Regex: '^<.*\.h>'
Priority: 4
- Regex: '^<.*'
Priority: 5
- Regex: '.*'
Priority: 6
# clang-format-3.8 IncludeIsMainRegex: (_spec)?$
IndentCaseLabels: false
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300 # JB put 60
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Middle
ReflowComments: true
SortIncludes: true
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto # JB put Cpp11
TabWidth: 8 # JB put 4
UseTab: Never
In QtCreator, go in Tools > Options... > Beautifier > Clang Format, add above YAML as new customized style.