Please note; these rules are not random; nor are they my personal preferences. They represent -- for the most part -- an emerging 'de facto' standard for how modern C++ code is developed.
C++14 "modern" style
- Don't use naked new/delete
- Prefer passing and returning by value
- Use unique_ptr unless you need sharing
- Return
unique_ptr
from factory functions, they can be turned intoshared_ptr
. - Don't use indexed for loop when foreach is enough.
- Look to algorithms instead of complex foreach loops
Try to avoid '-er' type classes (Manager, Controller, Handler etc). It's often hard to determine what their purpose is.
Sometimes you need a class whos only purpose is to handle a set of (logical) child classes. But they should normally be short and do only that. If actual business logic starts to sneak in to your manager class, it's either time to rename it or (better) move that logic somewhere else.
We try to follow the pitchfork propsal
We use a single, top level CMakeLists.txt
We also have a boot strapping Makefile
so you can just type make
after cloning.
Put the interface in the header and the implementation in the cpp file. The cpp file should include the headers as its first line, even if it is otherwise empty.
Use #pragma once
instead of ifdef guards; it is supported by all compilers
and is less error prone (and probably faster).
Sort your includes;
- The corresponding class h-file first.
- Then other local include files.
- Then 3rd party headers.
- Last std library includes.
Try to keep dependencies out of inlude files.
- LLVM as base
- Spaces for tabs
- 80 column limit
namespace ecp {
class StateMachine
{
public:
void doSomething(std::string const& name)
{
if (name.empty()) {
return;
}
}
};
} // namespace ecp
Camel case, Upper case for classes and structs, lower case for methods and variables.
Namespaces should be lower case and kept short.
Use UPPERCASE only for macros, not for enums and contants, to avoid collisions with existing macros.
If you need to name your member variables differently, use a trailing underscore;
ie myMemberVariable_
.
Priority; where to spend most time thinking about good names
- Classes
- Functions
- Member variables
- Function arguments
- Local variables
- For loop / small clause variables
ie Think really hard about what a class should be called so it conveys as clearly as possible what it does, and change the name if the responsibility of the class changes.
As a special case, function arguments should of course have good names, but you should not need to see them to understand the function.
The usage of a function should perferably be clear by it's name and the types of its arguments.
Distinguish between errors and nonerrors. A failure is an error if and only if it violates a function's ability to meet its callees' preconditions, to establish its own postconditions, or to reestablish an invariant it shares responsibility for maintaining. Everything else is not an error.
Herb Sutter
Check pre conditions at compile time if possible (static_assert). For errors, we use exceptions. We can catch and rethrow in jni layer.
clang-format
everything -- don't spend mental energy on formatting source code.Address sanitizer
andvalgrind
to find memory problems and leaks.clang-tidy
for liniting.cmake-format
to keep the CMakeLists.txt clean.
Aim for test driven design.
We use catch2 and put all our unit tests under tests/
Since you don't have tests for your tests;
Test code should be understandable and correct by inspection.
Also because test code is a good starting place to understand the rest of the code.
Avoid special test utility methods, or test base classes. Try to write top-down, understandable code.
Try to document API level methods and classes with descriptive text, using
//! comments like these
We can then use Doxygen to generate documentation.
Boost uses 80 columns. The clang standard library uses 80 columns. Most lines are shorter, so a longer linewidth wastes more screen real estate.
With clang-format
, you don't need to do line breaking manually, it will
format the code good enough to be readable.
It is univerally understood that long lines are hard to read. This is why newspapers format things into columns. For code this can break up statements too much and at some point it will make it harder to read. As far as I know there has been no studies made to figure out where the "breaking point" lies, but I think the concencus in the community is that it is below 80 characters.
Don't let the historical significance of the number fool you into thinking this is just a legacy rule. It's just a nice, even number that seems to be a good trade off between too long lines and two much wrapping.
I prefer fn(std::string const& arg)
instead of fn(const std::string& arg)
.
There has been a lot of debate about this, but the concesus is basically that
the first (east const) is more logical, but the second is the standard.
Either one is OK.
Some C++ programmers (mainly those used to C#) prefer to put a new line before every curly brace.
Java code never puts new lines before braces.
The de facto C++ standard is a mix, and not really a better alternative than either Java or C#, but since there is no standard that is universal, we may as well go with the de facto C++ standard.
The C++ community is moving away from CamelCase towards lower snake_case for everything. This guide still recommends the older more universal standard that is similar to Java and C#. I think this still reflects what most developers are used to, but it would be more correct to switch to snake case. We can still start types with upper case.