C++ Style Guide
Version 4.0. Maintained by Ken Rossato. Please include the version number in any correspondence.
Table of Contents
Code Guidelines
Architecture
- Have a clear hierarchy of ownership between objects.
Objects own their by-value member variables, containers own their contents, and so on. A clear ownership hierarchy will assist determinations of what references and pointers can safely be held without risk of dangling.
- Have a clear hierarchy of reference between types.
This is similar to the ownership hierarchy, but not necessarily the same. For example, std::vector owns objects but has no reference to their type because of templating. Infrastructure should be at the bottom of a type hierarchy and business logic at the top. [Lakos]
- In large projects, consider componentization.
Group classes into components by related responsibility. Keep a component's classes in a distinct directory and namespace. Components should be understandable in isolation from other components. Maintain a clear hierarchy of reference between components.
- Eschew circular ownership and circular type dependencies.
Ownership cycles can be broken by references and pointers. Dependency cycles can be broken by templates and polymorphism.
Memory Management
- Construct directly into target memory, preferably in contiguous blocks. Avoid individual heap allocations, which are expensive especially in multithreaded programs:
- Prefer to construct short-lived objects directly into locals
...than into managed pointers.
- Prefer to construct long-lived objects directly into parent objects, STL collections, or static memory
...than into managed pointers. Std::unique_ptr should only be necessary for implementation-hiding (with PIMPL) or polymorphism, and std::shared_ptr should only be necessary in very rare cases where ownership is truly shared. A clear hierarchy of ownership simplifies code and also helps make clear when it is safe to store references and observer pointers.
- Prefer "emplace" over "push" or "insert"
...methods for STL collections [EMC++ Item 42]
- Prefer std::array and std::vector
...over other container types. Prefer std::array to C-style arrays.
- Reserve estimated capacity for vectors.
Err on the side of wildly over-reserving, such as by a factor of 5 over expected typical usage. [ESTL Item 14]
- Prefer value types in classes over pointer types.
Has-a (composition) and is-a (inheritance) relationships can ensure members use neighboring memory and share an allocation, while still separating responsibilities (including construction and destruction). [EC++ Item 19]
- Consider Boost pool allocators for collections of objects with very large sizeof().
- Prefer clearing and reusing vectors and strings over replacing them with new copies.
Vector and string implementations may reuse previously allocated memory, saving calls to new/delete.
- Avoid explicit calls to "new" and "delete".
Using bare pointers to manage memory is exception-unsafe. Instead prefer creating variables by value in a class member field, a local, or inside an STL collection with "emplace". If individual heap allocation is necessary, prefer managed pointers (shared_ptr, weak_ptr, and unique_ptr), which automatically delete. Be sure to use make_shared for contiguous allocation with a shared pointer's control block. [EMC++ Item 21]
- Access memory efficiently:
- Select STL collections for the efficiency of their access pattern.
Prefer (in this order): std::arrays/vectors for iteration efficiency, std::deques/queues for insertion/deletion at the ends, std::lists for frequent insertion/deletion by iterator, std::maps for key-lookup, and std::sets for de-duplication. Understand the efficiency cost of every STL method you use.
- Consider algorithms that can make vectors competitive with other choices.
For example, you can delete from the middle of a vector in O(1) time when order doesn't matter (move-assign from back() and then pop_back()). As another example, functions from <algorithm> can perform a binary search on a sorted vector exactly as fast as traversing a std::map, if the vector doesn't frequently need to be re-sorted (if inserts and deletes are rare). [ESTL]
- Use iterators and ranged-for ("foreach") loops.
Assume operations on iterators are as cheap as their native equivalent (pointers, indexes, etc). Pass and store iterators by value. Cache begin() and end() when used in a loop and read STL documentation to understand which collection methods invalidate iterators.
- Prefer "auto" (with correct const-ness and ref-ness) for storing short-lived locals returned from functions,
...especially for iterators and ranged-for loops. In addition to being concise, it may avoid unintended casts. [EMC++ Item 5]
- Return new objects by value, the compiler will elide the copy
...(or worst-case convert it to a move). Understand the C++17 rules for guaranteed copy ellision, since chances are even pre-C++17 compilers will make this optimization. Understand the usual cases in which named return value optimization (NVRO) is performed. For polymorphic values, returning by unique_ptr may be desirable. Don't ever return a reference (even an rvalue reference) or pointer to a local variable in a function: if it doesn't lead to a memory error it will be purely by coincidence, as it will be a reference to an expired value on the stack.
- Don't "return std::move(x);"
...when "x" is a local variable. This behavior is already ensured by the standard, and this syntax may inhibit return value optimization.
- Don't pass strings or collections by value or otherwise copy them.
Prefer passing by (const-)reference instead. Yes, moves may make passing by value cheaper, but...
- Don't assume a move operation is inexpensive.
A move should be no more expensive than a copy, but isn't always constant-time. In particular consider the short-string optimization as a reason to avoid moves of strings. [EMC++ Item 29]
Exceptions
- Avoid the runtime and maintenance overhead of error checking when actions are expected to succeed (the "happy path"):
- Prefer throwing exceptions instead of returning error codes.
Explicit error checking introduces a non-zero performance penalty, as well as having to properly maintain correctness for all possible errors, which could be difficult to do consistently in a large software project. The "long jump" of going directly from the generation of an exception to the error handler in a catch-block avoids the need to check for the same error repeatedly down a function chain.
- Prefer versions of library calls that throw exceptions instead of returning empty values or errors
...(prefer std::map::at).
- Understand the situations where simplicity and performance favor returning a sentinel value instead.
"Throw" has a significant performance overhead, so while exceptions should be prefered by default, it should only be in the (normal) case where failure is significantly less likely than success. For areas of code that are tightly looped or performance-critical, or where failure is a common expected case, or where the error is immediately handled by the calling function, status codes or null/empty return values may be preferable. These cases should be carefully documented in comments, as they violate the RAII expectation that resources returned by a function are valid.
- For every exception you throw, know where you intend to catch that exception.
Similarly, for every try-catch block, know where you expect exceptions to be thrown. A thrown exception is a "long jump" directly to an an error handler, and as such the exception and handler should be thought of in pairs. Do not throw without thinking about how it will be caught, or catch without thinking about what will be thrown.
- Don't use catch(...) (catch-all) or catch an exception just to log or otherwise ignore it.
A thrown exception means something in your program didn't do what you told it to, and is now in a different state than you expect it to be in. If you don't know what this exception is or how to clean up after its occurance, this creates the potential for subsequent incorrect behavior, including possible data corruption and security violations. It may be much more preferable to allow the exception to propagate, even if this means the crash of the program (which will at least provide a meaningful backtrace close to the original error). See also the section on Destructors.
Constructors
- Enable efficient creation of a class:
- Prefer constructors over factories
...for non-polymorphic classes. This allows direct emplacement in STL classes and shared_ptrs. It is syntactically possible to implement the factory pattern without pointers using templates, but it's convoluted and ugly.
- Declare constructors "constexpr" when possible.
Understand the uses of constexpr to initialize statically-sized resources such as constant tables. [EMC++ Item 15]
- Prefer constructor delegation (with "constexpr" when possible)
...over duplicate code or non-constructor "init" methods.
- Avoid the PIMPL idiom,
...except in the specific case of writting library interface classes. Don't be eager to hide the memory layout of a class from yourself. This mandates that constructors invoke an additional heap allocation, causing heap fragmentation, and can add unnecessary indirection for otherwise inlineable function calls.
- Ensure correct copy and move behavior:
- Examine the correctness of the default behavior (member-wise action) of the "big 5",
...and define these functions if that behavior is insufficient. If a class is used in an STL container, all of the big 5 could be used:
- Copy constructor
- Copy assignment operator
- Move constructor
- Move assignment operator
- Destructor
- Best if possible is to implement the "big 0"
Design your class so that it is correct with the default (memberwise) destructor, copy, and move operators.
- If any of the "big 5" are declared, prefer to declare all of them,
...whether by writing, defaulting, or deleting them. This will reduce confusion.
- If any of the "big 5" are declared, move operations MUST be declared
...(possibly with "default"), otherwise the compiler will not automatically create one, and operations that should be moves will be copies instead. [EMC++ Item 17]
- If a class is non-copyable, move operations must be declared to be usable in some STL containers.
One potential reason to disallow both copies AND moves is for Observers or callbacks that depend on continued validity of an object at an address (but consider managed pointers or refactoring).
- Ensure correct usage of std::move
...inside move constructors and other functions with rvalue-reference arguments, to avoid accidentally converting rvalues to lvalues and thus converting moves to copies. [EMC++ Item 23].
- Designate move constructors and move assignment operators as "noexcept" whenever possible
...for more efficient STL operations. Most importantly, this will determine whether moves or copies are used when vectors are resized. [EMC++ Item 14]
- Avoid copy- and move-assignment operators that leave the object in an inconsistent state on exception.
The "construct-and-swap" idiom is a useful mechanism to make assignment exception-safe. [XC++]
- Other constructor issues:
- Be aware constructors will not call the destructor on exception,
...but will destruct members. Guard against leaks of resources allocated in constructors with try/catch if necessary.
- Use "explicit" on single-argument constructors
...to guard against constructors being used by the compiler for implicit casting.
- Ensure Resource Acquisition Is Initialization (RAII).
Constructors should either throw an exception or return an object that is immediately usable just like any other instance of that object, without any need for another call to an "init", "connect", or "start" method. A possible exception to this rule is a class with a state-machine design. In a similar vein, shared_ptrs should always be dereferenceable; avoid constructing a shared_ptr with the default constructor or resetting one to empty, unless this is in a performance-critical area of code and well-documented. The goal should be to avoid the need to litter code with null checks "just in case".
Destructors
- Avoid the subtle pitfalls of exceptions in destructors:
- A C++ destructor has two responsibilities: (1) to release resources, and (2) to not throw exceptions. Any application goals beyond that must be subsidiary to those first two responsibilities.
- Ensure destructors do not throw.
An exception propagating outside a destructor can, under the right conditions (and always, from C++11 onward), result in program termination. Care must be taken to ensure that logic inside a destructor not allow exceptions to be generated, nor call standard library (or other library) functions that can throw an exception. If a destructor must make dangerous calls, it should catch and handle possible exceptions consistent with the guidelines in the Exceptions section. [EC++ Item 8]
- Ensure all logic paths that reclaim resources cannot be skipped by exceptions.
If you use a try-catch block in a destructor, be 100% sure that throwing an exception will not skip code that frees memory, closes file handles, drops weak_ptrs/observers, or otherwise releases resources. These are operations that must succeed.
- Prefer dividing destructor activities into shorter destructors in smaller classes.
The unique_ptr class in C++11 is an extreme and instructive example: it exists only to ensure memory pointed to by a pointer gets deallocated on destruction. Classes organized around individual resources can help compose larger classes, ensuring destruction behavior is correct without the need for complicated error-handling routines or even an explicit destructor.
- Do not blindly catch-all in destructors.
See the corresponding guideline in the Exceptions section. An exception indicates that a requested operation did not occur and that your program is now in an undesired state. Allowing a program to proceed after this point could result in undefined behavior, including data corruption or security violations. It may be more safe to allow a program to terminate, since at least you will have a backtrace closer to the underlying issue.
- Avoid calling business logic in destructors.
The surest way to deal with the above problems is to follow the rule of thumb "do nothing in destructors, unless not doing it would result in a leak". Release file handles and delete memory (or even better, use members variables that do it for you when THEY destruct), but do not attempt to perform any "graceful cleanup" logic. Any such logic should be in functions (e.g.: "Close") that get called before destructors run. [EC++ Item 8]
Templates
- Generate function and operator overloads on-demand with templates:
- Template individual class methods and functions when an algorithm works on multiple types
...such as when passing an argument onward to a stream operator for logging or output. This may require a non-templated "base case". Remember the Robustness Principle: be generous in what you accept. [EC++ Item 45]
- Prefer templates over std::function for passing functors.
Many types can implement the parentheses operator: classes, function pointers, lambdas, std::binds, and std::functions. Because std::function has to be able to encapsulate all the others, it has the greatest level of run-time overhead. Instead of accepting std::functions, template function arguments on functor type. [EMC++ Item 5]
- Pass templated function arguments by (const-)reference.
Do not code as if a templated argument will be of a small type, unless this is a reasonable assumpion in context (e.g. if the argument is supposed to be an iterator type).
- Correctly use universal ("forwarding") references and std::forward
...for templated methods that can accept both lvalue- and rvalue-references. This will preserve the "rvalue"-ness of the argument, correctly choosing between copy and move operations in subsequent function calls. [EMC++ Item 25]
- Avoid universal reference parameters that come from a class template.
These aren't universal references. Since a class's template type will never be a reference type, "T&&" will always resolve to an rvalue-reference and won't bind to lvalues. To workaround, best is to explicitly write two versions of the function for "T&" and "T&&", but also possible is to template the method on another type "U&&". [EMC++ Item 24]
- Use templates to separate implementation from interface (duck-typing):
- Template on types above your level of responsibility.
Infrastructure code should not have dependencies on business-logic code, doing so introduces unnecessary coupling and makes it harder to understand behavior in isolated chunks. Conversely, don't go nuts templating out references to sister classes within a logical component, or templating out infrastructure code in business logic, as this obfuscates a true dependency and unnecessarily bloats code size and compile times.
- Use polymorphism to hide internal templating from external components
...such as when using trait classes/structs. This separates unnecessary implementation and policy details from a consumer, and also "breaks the chain of templating", meaning that consuming classes don't themselves need to be templated on dependent types.
- Use "extern template" declarations in header files to minimize recompilations of common template specializations.
Then specify that template specialization in a cpp file. This goes a long way toward reducing the compile-time overhead of using templates.
Polymorphism
- Avoid the runtime overhead of polymorphism when possible:
- Prefer non-polymorphic classes to polymorphic classes.
Polymorphic classes are classes with "virtual" methods. Virtual methods result in the creation of a class "vtable", an additional layer of runtime indirection for function calls.
- Prefer templates to polymorphism.
Polymorphism is solely to solve the problem of dynamic dispatch, which is a need that shouldn't be terribly frequent. True use cases for dynamic dispatch are activities such as selecting an action from user input, or queuing activities for a threaded handler. Otherwise, classes that have pluggable strategy/policy behavior or dependencies are candidates for templating instead of polymorphism.
- Minimize the runtime overhead when polymorphism is necessary:
- Prefer polymorphic classes over other forms of dynamic dispatch.
E.g. prefer over designs that use function pointers, lambdas, or std::bind.
- Isolate non-polymorphic behavior from polymorphic behavior
...with a "wide pointer" pattern: a non-polymorphic owner class with common data members and a std::unique_ptr to a polymorphic type. Ensure the constructor implements the factory pattern so you can directly emplace these "wide pointers" into collections.
- Declare destructors of a polymorphic base class, and declare them "virtual".
Also understand the occasional use cases for virtual destructors in otherwise non-polymorphic base classes (such as when returning pointers with the factory pattern). [EC++ Item 7]
- Avoid making non-polymorphic methods virtual
...except for destructors as noted above.
- Use polymorphism safely and correctly:
- Use the "override" keyword
...on implementations of virtual methods in derived classes to avoid typos or implementing the wrong type signature (which otherwise will silently succeed but never be called). [EMC++ Item 12]
- Define overridden functions as "virtual"
...in derived classes (this is the default, but be explicit).
- Define overridden functions as "noexcept" if the base class version is "noexcept"
...(this is the default, but be explicit).
- The full correct signature of an override of std::exception::what is:
- "virtual const char *what() const noexcept override"
- Do not "hide" base-class functions by writing a non-virtual function with the same name.
Use a different name. If that doesn't work because the purpose is static polymorphism (templating), then declare the function virtual and use proper dynamic polymorphism. You never know when a derived class will be consumed as a reference to the base class. [EC++ Item 33]
Threading
- Prefer an asynchronous proactor model over threading,
...such as implemented by the Boost::ASIO library. Asynchronous programming with modern OS support (such as epoll on Linux) is very fast, and ensures additional overhead isn't incurred by the frequent locking of global state that's needed by threaded programs. In cases where there is significant mutable global state, a single-threaded asynchronous application may outperform a multi-threaded implementation. In cases with minimal mutable global state (such as a web server), a proactor can be combined with threads to implement a high-performance M:N task-servicing model.
- Give threads logically independent work
...that can be completed without dependence on other threads. This is important to minimize the amount of synchronized state between threads, which in addition to improving correctness, also improves performance on modern processors.
- Communicate between threads cautiously.
Share data using thread-safe queuing algorithms that can be simultaneously pushed into and pulled from. Do not be eager to directly share state with data structures protected by locks.
- Avoid Threading.
Threading is difficult to get right and easy to get wrong.
Miscellaneous
- Prefer nullptr to 0 or NULL
...to avoid incorrect type inferences and implicit casts. [EMC++ Item 8]
- Prefer C++11 "using"-style type aliases to typedefs
[EMC++ Item 9]
- Use type aliases vigorously inside a class, or even better template on the type.
Consider type aliases to factor out common references to STL types within a class.
- Prefer C++11-style scoped enums
...("enum class"). [EMC++ Item 10]
- Prefer forward-declaring a class to including its header
...when the class is only referred to in that file as a pointer or reference, and no methods are invoked. [EC++ Item 31]
- Don't use std::move on a dereferenced managed pointer
...unless you're 100% sure the managed pointer won't be used again elsewhere.
- Aside from move constructors and universal references, explicit uses of rvalue-reference parameters should be rare,
...and should be studied to ensure their correctness. Use std::move to preserve the "rvalue"-ness of an rvalue-reference.
- Don't use #define for constants,
...use constexpr static types within a class (even a completely static class) instead. Strings that need to use C's compile-time concatenation syntax ("string1" "string2") may be an exception, as the alternative requires quite a bit of template magic, but the resulting concatenated value should then be stored in a static constant. [EC++ Item 2]
Mechanical Style
Compilation
- Code should at least be compiled with "-Wreturn-type -Wreorder"
...as these warnings signal unexpected behavior of the compiled code. Consider "-Wall", if the output doesn't yield too many false positives.
Directory Structure
- include/ should contain publicly installed header files
- src/ should include code for the installed executable/library, including any private header files
- test/ should include any testing code not included in the installed executable/library
- Within src/, a single level of subdirectory should be used to organize files by functional area.
There should be a clear hierarchy-of-reference between functional areas (application logic should refer to infrastructure logic which should refer to utility logic, with no back-references. Use templates if necessary.). All files within src/ should be in one of these subdirectories, with exceptions for a file including "main" and any argument-parsing logic. [Lakos]
- Should be suffixed ".hpp" for maximum editor detectability.
- Should contain one major class definition
...and be named after that class with the same casing.
- Should have a unique #ifndef, #define, #endif include-guard derived from the file name.
- Should be namespaced with full literal "namespace" blocks
...ideally with a project-level namespace and then namespaces following the directory structure. No "using" statements should be used, as these can pollute files that include this header.
- Should include header files needed for compilation of this header file on its own
...i.e. for any classes stored by value or operated on with the "." or "->" operator.
- Should contain forward-declarations for any classes stored by reference or pointer.
- Order of includes should be standard library headers in alphabetical order
...followed by other library headers in alphabetical order, followed by headers from the current project in alphabetical order.
- Declare single-line functions inline (but not using the "inline" keyword)
...especially getters and setters.
Source Files
- Should be suffixed ".cpp"
- Should have includes in the same order as header files
...except it should begin with a single include of the same name as the cpp file if such a file exists.
- Should have a single "using namespace X;" statement to refer to its own component.
Other "using" statements should spell out full types, and not import entire namespaces. "Using namespace std;" is not recommended, but can be used in accordance with personal preferences.
- Cpp files for templated classes that need to be headers should be named "FileName-inl.hpp"
...and included within their parent header as the last statement. An include guard isn't necessary as this file will only be included from one other file.
Spacing
- Prefer "Allman", also known as "BSD", style:
4 spaces with open and close braces on their own line, aligned with the preceding declaration or control statement. This creates visual space that makes code more readable on modern displays.
- Lines over 80 characters should be broken into multiple lines
...and indented on an ad-hoc basis (i.e. to align with opening parentheses). This shouldn't affect indentation of subsequent lines.
- All functions and control flow statements should follow the Allman style regardless of number of statements:
- Braces should always be on separate lines, even if the body is brief.
- Control flow statements should always have braces even if there is only one statement in the body.
- One potential exception is "if () return/continue/break;" due to the fact that the interruption of control flow is more semantically associated with the parent scope. This should NEVER be on two lines, however, due to the potential for a "goto fail"-style bug.
- Lambdas that can comfortably fit on a single line can remain on a single line.
- No more than two levels of nested indenting in a function.
A "for" or "while" loop with "if" statements is the ideal maximum level of nesting. After that, make a function.
"Switch" Statements
- Fallthrough between immediate adjacent "case" statements is ok without comments.
Fallthrough after code in between "case" statements must be commented.
- "Case" statements should be aligned with "switch"
...and contain nothing else on the line.
- No control flow (if/for/while) within a "switch" block.
Make a function.
Casing
- Namespaces, classes, structs, enums, functions, static member variables, and type names (typdefs, usings, and template type arguments) should be cased LikeThis.
- Local variables and public member variables should be cased likeThis.
- Protected and private member variables should be cased m_likeThis.
- File-level (including anonymous namespaces) and global-level constants should cased LIKE_THIS.
- Global-level non-constant variables should be cased gLikeThis.
- Exceptions should be made to follow casing rules for any external library element whose name is borrowed for a method or member. Examples: "std::ostream &ostream() { ... }", "boost::asio::io_service m_io_service;"
Classes
- "public", "protected", and "private" statements should align with the class statement and be listed in that order.
Sections may be repeated if necessary to control order of initialization.
- The first declarations should be the constructors
...in increasing number of arguments, followed by the destructor (if present).
- Single argument constructors should use "explicit".
Constants
- Constants should be const variables
...and defined in as restrictively a namespace is as feasible: within an anonymous namespace in a cpp file, or as a class static constant are both ideal options.
- Constants should not be macros
...unless defined externally e.g. by the build system.
References and Supplemental Reading Material
- Effective C++, Third Edition, Scott Meyers [EC++]
- (highly recommended for the beginner- to advanced-level programmer)
- Effective STL, Scott Meyers [ESTL]
- Effective Modern C++, Scott Meyers [EMC++]
- (highly recommended for the advanced- to expert-level programmer)
- Exceptional C++, Herb Sutter [XC++]
- Large-Scale C++ Software Design, John Lakos [Lakos]
This work is licensed under a Creative Commons Attribution 4.0 International License.