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 lower_snake_case.
Private attributes are to be prefixed with “m_”. For example: m_size.
All macros are named in ALL_CAPS
Template-arguments are named in PascalCase
Concepts, once they are available, will be named Snake_case_with_upper_first_letter.
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: FOOBAR_HPP
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 SOME_MACRO.
The above rule also applies for include-guards: MYLIB_FOOBAR_HPP
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. (foo__bar)
Any identifier that starts with an underscore and is part of the global namespace.
(These 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 allways 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 usefull 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: std::vector, std::unique_ptr, std::ofstraem, …)
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 try-catch-blocks.
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, allways use the appropriate mutex-managment classes like std::unique_lock and std::lock_guard.
Do not use build-in arrays if you are not implementing an array-like container from scratch.
Your default-container should be std::vector.
If you are unsure whether you should use std::vector, just use std::vector.
If you want/need a container of compile-time fixed size, you may use std::array.
Other containers like std::list and std::deque may be used after carefull 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 std::unique_ptr.
In the very rare event that this is still not enough, you may additionaly use std::weak_ptr.
If you feel that this is still insufficient:
If you are implementing fundamental, generic datastructures in their own class-templates, you may use raw-pointer in combination with allocators. Be carefull!
Otherwise redo your design for it is almost certainly to complicated, for normal humans to understand the controll-flow in 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 awfull trend that needs to stop. C++ works by far best when you just use normal stack-variables (even if those use the heap internally like std::vector).
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.
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-refence 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 the override-“keyword”.
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 std::back_inserter)
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 <numeric>.
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. (qsort → std::sort, memcpy → std::copy (yes, std::copy is 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::open and 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. This rule is however not intended for you then.)
Be aware that C++11 made streams movable, so use that instead of another pointer-indirection. (libstdc++ implemented that very late though, so you may ignore this, if you have to support old versions of it.)