This coding-standard is still work in development. It is however complete enough to give a first impression of what I expect from code that should become part of projects under my controll.
It is very important to understand that few things here are to be understood as absolute gospel. These are guidelines, not laws.
The naming-conventions here basically boil down to “Do as the standard-library does” and only deviates from that if there are very serious technical reasons or when it doesn’t provide any guidence.
- Classes, functions (including methods), variables, constants, aliases, namespaces and templates are all named in
- Private attributes are to be prefixed with “
m_”. For example:
- All macros are named in
- Template-arguments are named in
- Concepts, once they are available, will be named
- All files are named in
lower_snake_case and use on of the following file-extensions:
.cpp for all source files that are directly compiled
.hpp for headers
.tcc may be used for files that are technically headers, but semantically implementation. For example if there is a header that declares a lot of templates without defining them, the definitions may be put into a file with this extension. These files must not be part of the public interface!
- Include-guards should in general be named like the files name converted to uppercase and all special-characters replaced with underscores. For example:
For libraries and modules of large projects, the following rules apply in addition:
- All Macros must be prefixed with the name of the appropriate namespace converted to uppercase. For Example:
MYLIB_SOME_MACRO instead of
- The above rule also applies for include-guards:
The following names must never be used under any circumstances, since they are reserved for the implementation by the C++-standard:
- Any identifier that starts with an underscore followed by a capital letter.
- Any identifier that contains double-underscores. (
- Any identifier that starts with an underscore and is part of the global namespace.
(The last rules shouldn’t be necessary here, but they are violated extremely often, especially for include-guards, so let’s repeat them!)
- In general opening braces do not belong on their own line, with the obvious exception of braces that exist solely for the purpose of creating a new scope. You may however use your own judgement to decide in corner-cases.
- Indent with tabs, align with spaces.
- the statements in an if/else-block or a loop-body must always be wrapped by braces.
- Very short code-blocks don’t have to be put on their own lines, if it helps readability
- Use the highest sane Warning-level. For GCC and clang that includes the following options:
-Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion
Treat all warnings as errors.
- Do not use options like clangs
-Weverything that activate all warnings, since quite a few of those are not useful in any way for the vast majority of projects. For instance:
-Wc++98-compat warns about the use of C++11-features.
- Write high-level code. It has often higher performance while being easier to understand.
- Manage all your Ressources with RAII!
- All state-holding classes should fall into one of these categories:
- Ressource-Managment-classes that only manage one kind of ressource and do noting besides that. (For example:
- Bussines-Logic-classes that are used to implement the business-logic and that do not manage any ressources at all. In general these classes shouldn’t need customn special member-functions (assignment-operator, destructor, …).
- All code must be exception-safe, even if no exceptions are used. This must be accomplished by use of RAII, not by
- Bear in mind that memory is just one kind of ressource and that there are many others like filehandles, sockets, mutex-locks, threads, …
- Never lock and unlock mutexes manually, always use the appropriate mutex-managment classes like
- Do not use build-in arrays if you are not implementing an array-like container from scratch.
- Your default-container should be
- If you are unsure whether you should use
std::vector, just use
- If you want/need a container of compile-time fixed size, you may use
- Other containers like
std::deque may be used after careful consideration if some part of their interface is required. They may also be used as an optimization if measuring shows sufficient performance-advantages
- Prefer normal (stack-) variables, if those are sufficient. This should be over 95% of the time.
- If you really have to put a variable onto the free-store, use a
std::unique_ptr and allocate it with
std::make_unique; never use
new here. This should be sufficient for over 90% of the remaining times.
- If that is still insufficient, you may use a
std::shared_ptr. Those may be created with either
std::make_shared or, if you already have one of those, from a
- In the very rare event that this is still not enough, you may additionaly use
- If you feel that this is still insufficient:
- If you are implementing fundamental, generic datastructures in their own class-templates, you may use raw-pointers in combination with allocators. Be careful!
- Otherwise redo your design for it is almost certainly to complicated and likely overly hard to understand the control-flow of it.
- A note on
new: The allocating
new shouldn’t be directly used for basically anything these days, since stdlib-containers and smart-pointers with their
make_*-functions have taken over basically all legit use-cases. At this point it should be treated as at least as bad as goto: It still has a tiny amount of legit use-cases, but most people don’t run into those and should avoid it like the plague. (This paragraph does not talk about placement-new, which is an experts-feature that hasn’t seen a similar history of abuse and is still desperatly needed in quite a few situations.)
- A note on shared-pointers: With the arrival of C++11 a lot of new projects seem to have started using it for almost everything, similar to the way that pointers are used in Java. While this is an improvement over the previous use of raw-pointers to memory allocated with
new, it is still a very awful trend that needs to stop. C++ works by far best if you just use normal stack-variables (even if those use the heap internally like
- If you don’t change a variable, make it const.
- If you have to use ugly trickery to make a variable const, don’t make it const!
- If a variable is used from several places, make sure that it is const in as many of them as possible.
- Contrary to popular opinion, mutability is not a problem, but shared mutability is a big one, even without the presence of multithreading.
- Try to keep the complexity in functions low and the body short.
- Use more of them, so that they can become smaller.
- Just because you don’t do something twice, doesn’t mean that it shouldn’t be in it’s own function!
- Use Lizard regularly. No function should ever have a cyclomatic complexity higher than 15. (It’ possible to write floating-point parsers in less!) Usually you shouldn’t need to leave the lower single-digits at all!
- Limit the number of loops in a function extremely: If you need more than one, seriously consider what could reasonably be moved to other functions. This is not a hard ban, but a very strong goal.
- Pass functions-arguments by const reference as default.
- If the type of the argument is just an empty tag-type, pass them by value instead.
- If the type of the argument is an integral datatype, a floating-point-type, a pointer or a thin wrapper around any of those (that is documented to be that), you may pass them by value as well.
- If the argument is templated and an iterator or a callable object, you may pass them by value to resemble stdlib-algorithms.
- If the function is a constructor and the argument is used to initialize a member, you may pass it by value and move it.
- If you need an rvalue you may either take the argument by value or by rvalue-reference.
- Avoid mutable references as arguments, except for operators and very few special-cases where they are required to blend in with common idioms.
- Never take arguments by const rvalue!
- Be aware that a template-parameter that is used for a rvalue-reference is actually a forwarding-reference!
- If a class has no state at all and is not used as some kind of type-tag or similar thing (policy-type for templates, meta-function, …), chances are, that whatever it models, it shouldn’t be a class.
- Don’t make classes virtual if there is no good reason for it. Especially don’t add a virtual destructor in that case.
- If you override a member-function of a base-class, annotate that with
- Don’t abuse classes as namespaces. Instead use, you guessed it, namespaces.
- Never declare more than one member-variable per line.
- Prefer the use of stdlib-algorithms to self-written loops and conditionals, since those are in general correct, more semantic due to having names and experienced programmers may already know them.
- For that purpose: Make sure that you understand iterators and use them liberally. Be aware of the existence of iterators like the stream-iterators or the insertion-iterators (see for instance
- A huge lot of things can be done with stdlib-algorithms. Make sure that you know them and don’t forget that not all of them are in the
<algorithm>-header, but also some in
- Do not use algorithms from C, if their is also a C++-version. Those are often slower, more error-prone uglier to use and less general. (
std::copy is just as fast, since it calls memmov if possible in both libc++ and libstdc++), …)
- Do not use
std::endl. Contrary to very popular opinion the difference compared to
'\n' is not, that only
std::endl is a portable newline (both are!), but that
std::endl also flushes the buffer, which is rarely ever needed and most of the time just premature pessimization). If you really want to flush the buffer, just follow the newline with a
std::flush so that everyone know that this is an explicit decission:
stream << "Hello World\n" << std::flush;
- Do not use
std::fstream::close. Instead just use the constructor and the destructor. (If you really need to open late or close early for some reason, those are fine, but usually that also means that your design is suboptimal.)
- Be aware that C++11 made streams movable, so use that instead of adding another (smart-)pointer-indirection.