modern-cpp-cheatsheet
modern-cpp-cheatsheet copied to clipboard
Best practices of Modern C++
Effective Modern C++ Cheatsheet
Shorthands
- ref(s): reference(s)
- op(s): operation(s)
Terms
- lvalue: typically an expression whose address can be taken e.g a variable name (
auto x = 10;) - rvalue: an expression whose address cannot be taken in C++ i.e before C++11 e.g literal types (
10) - lvalue-ref(erence): reference to an lvalue type typically denoted by
&e.gauto& lvalue_ref = x; - rvalue-ref(erence): reference to an rvalue type typically denoted by
&&e.gauto&& rvalue_ref = 10; - copy-operations: copy-construct from lvalues using copy-constructor and copy-assignment operator
- move-operations move-construct from rvalues using move-constructor and move-assignment operator
- arguments: expressions passed to a function call at call site (could be either lvalues or rvalues)
- parameters: lvalue names initialized by arguments passed to a function e.g
xinvoid foo(int x); - callable objects: objects supporting member
operator()e.g functions,lambdas,std::functionetc - declarations: introduce names and types without details e.g
class Widget;,void foo(int x); - definitions: provide implementation details e.g
class Widget { ... };,void foo(int x) { ... }
Chapter 1. Deducing Types
Item 1: Understand template type deduction
- Deduced type of T doesn't always match that of the parameter (i.e ParamType) in template functions
- For lvalue-refs/rvalue-refs, compiler ignores the reference-ness of an arg when deducing type of T
- With universal-refs, type deduction always distinguishes between l-value and r-value argument types
- With pass-by-value, reference-ness,
constandvolatileare ignored if present in the ParamType - Raw arrays
[]and function types always decay to pointer types unless they initialize references
Item 2: Understand auto type deduction
autoplays the role ofTwhile its type specifier (i.e includingconstand/or ref) as ParamType- For a braced initializer e.g
{1, 2, 3},autoalways deducesstd::initializer_listas its type - Corner case:
autoas a callablereturntype uses template type deduction, not auto type deduction
Item 3: Understand decltype
decltype, typically used in functiontemplates, determines a variable or an expression's typedecltype(auto), unlikeauto, includes ref-ness when used in thereturntype of a callable- Corner case:
decltypeon lvalue expression (except lvalue-names) yields lvalue-refs not lvalues
Item 4: How to view deduced types?
- You can update your code so that it leads to a compilation failure, you will see the type in diagnostics
std::type_info::name(andtypeid()) depends upon compiler; use Boost.TypeIndex library instead
Chapter 2. auto
Item 5: Prefer auto declarations
autoprevents uninitialized variables and verbose declarations (e.gstd::unordered_map<T>::key_type)- Use
autoespecially when declaring lambdas to directly hold closures unlikestd::function
Item 6: How to fix undesired auto type deduction?
- Use
autowithstatic_cast(a.k.a explicitly typed initializer idiom) to enforce correct types - Never use
autodirectly with invisible proxy classes such asstd::vector<bool>::reference
Chapter 3. Moving to Modern C++
Item 7: Distinguish between () and {} (aka braced/uniform initializer) when creating objects
- Braced initializer i.e
{}prevents narrowing conversions and most vexing parse while()doesn't - During overload-resolution,
std::initializer_listversion is always preferred for{}types - Corner case:
std::vector<int> v{10, 20}creates a vector with 10 and 20, not 10ints initialized to 20.
Item 8: Prefer nullptr to 0 and NULL
- Don't use
0orNULL, usenullptrof typenullptr_twhich represents pointers of all types!
Item 9: Prefer alias declarations to typedefs
- Alias declarations (declared with
usingkeyword) support templatization whiletypedefsdon't - Alias declarations avoid 1)
::typesuffix 2)typenameprefix when referring to other typedefs
Item 10: Prefer scoped enums to unscoped enums
- Use
enum classinstead ofenumto limit scope of anenummembers to just inside theenum enum classes useintby default, prevent implicit conversions and permit forward declarations
Item 11: Prefer public-deleted functions to private-undefined versions
- Always make unwanted functions (such as copy-operations for move-only types)
publicanddelete
Item 12: Always declare overriding functions override
- Declare overriding functions in derived types
override; usefinalto prevent further inheritance
Item 13: Always prefer const_iterators to iterators
- Prefer
const_iteratorstoiteratorsfor all STL containers e.gcbegininstead ofbegin - For max generic code, don't assume the existence of member
cbegin; usestd::begininstead
Item 14: Declare functions noexcept if they won't emit exceptions
- Declare functions
noexceptwhen they don't emit exceptions such as functions with wide contracts - Always use
noexceptfor move-operations,swapfunctions and memory allocation/deallocation - When a
noexceptfunction emits an exception: stack is possibly wound and program is terminated
Item 15: Use constexpr whenever possible
constexprobjects are alwaysconstand usable in compile-time evaluations e.gtemplateparametersconstexprfunctions produce results at compile-time only if all of their args are known at compile-timeconstexprobjects and functions can be used in a wider context i.e compile-time as well as runtime
Item 16: Make const member functions thread-safe
- Make member functions of a type
constas well asthread-safeif they do not modify its members - For synchronization issues, consider
std::atomicfirst and then move tostd::mutexif required
Item 17: Understand when your compiler generates special member functions
- Compiler generates a default constructor only if the class type declares no constructors at all
- Declaring destructor and/or copy ops disables the generation of default move ops and vice versa
- Copy assignment operator is generated if: 1) not already declared 2) no move op is declared
Chapter 4. Smart Pointers
Item 18: Use std::unique_ptr for exclusive-ownership of resource management
std::unique_ptrowns what it points to, is fast as raw pointer (*) and supports custom deleters- Conversion to a
std::shared_ptris easy, therefore factory functions should always returnstd::unique_ptr std::array,std::vectorandstd::stringare generally better choices than using raw arrays[]
Item 19: Use std::shared_ptr for shared-ownership resource management
std::shared_ptrpoints to an object with shared ownership but doesn't actually own the objectstd::shared_ptrstores/updates metadata on heap and can be up to 2x slower thanstd::unique_ptr- Unless you want custom deleters, prefer
std::make_shared<T>for creating shared pointers - Don't create multiple
std::shared_ptrs from a single raw pointer; it leads to undefined behavior - For
std::shared_ptrtothis, always inherit your class type fromstd::enable_shared_from_this
Item 20: Use std::weak_ptr for std::shared_ptr-like pointers that can dangle
std::weak_ptroperates with the possibility that the object it points to might have been destroyedstd::weak_ptr::lock()returns astd::shared_ptr, but anullptrfor destroyed objects onlystd::weak_ptris typically used for caching, observer lists and prevention of shared pointers cycles
Item 21: Prefer make functions (i.e std::make_unique and std::make_shared) to direct use of new
- Use make functions to remove source code duplication, improve exception safety and performance
- When using
new(in cases below), prevent memory leaks by immediately passing it to a smart pointer! - You must use
newwhen 1) specifying custom deleters 2) pointed-to object is a braced initializer - Use
newwhenstd::weak_ptrs outlive theirstd::shared_ptrs to avoid memory de-allocation delays
Item 22: When using Pimpl idiom, define special member functions in an implementation file
- Pimpl idiom puts members of a type inside an impl type (
struct Impl) and stores a pointer to it - Use
std::unique_ptr<Impl>and always implement your destructor and copy/move ops in an impl file
Chapter 5. Rvalue references, move semantics and perfect forwarding
- Move semantics aim to replace expensive copy ops with the cheaper move ops when applicable
- Perfect forwarding forwards a function's args to other functions parameters while preserving types
Item 23: Understand std::move and std::forward
std::moveperforms an unconditional cast on lvalues to rvalues; you can then perform move opsstd::forwardcasts its input arg to an rvalue only if the arg is bound to an rvalue name
Item 24: Distinguish universal-refs from rvalue-refs
- Universal-refs (i.e
T&&andauto&&) always cast lvalues to lvalue-refs and rvalues to rvalue-refs - For universal-ref parameters, auto/template type deduction must occur and they must be non-
const
Item 25: Understand when to use std::move and std::forward
- Universal references are usually a better choice than overloading functions for lvalues and rvalues
- Apply
std::moveon rvalue refs andstd::forwardon universal-refs last time each is used - Similarly, also apply
std::moveorstd::forwardaccordingly when returning by value from functions - Never return local objects from functions with
std::move! It can prevent return value optimization (RVO)
Item 26: Avoid overloading on universal-references
- Universal-refs should be used when client's code could pass either lvalue refs or rvalue refs
- Functions overloaded on universal-refs typically get called more often than expected - avoid them!
- Avoid perf-forwarding constructors because they can hijack copy/move ops for non-
consttypes
Item 27: Alternatives to overloading universal-references
- Ref-to-const works but is less efficient while pass-by-value works but use only for copyable types
- Tag dispatching uses an additional parameter type called tag (e.g
std::is_integral) to aid in matching - Templates using
std::enable_if_tandstd::decay_twork well for universal-refs and they read nicely - Universal-refs offer efficiency advantages although they sometimes suffer from usability disadvantages
Item 28: Understand reference collapsing
- Reference collapsing converts
& &&to&(i.e lvalue ref) and&& &&to&&(i.e rvalue ref) - Reference collapsing occurs in
templateandautotype deductions, alias declarations anddecltype
Item 29: Assume that move operations are not present, not cheap, and not used
- Generally, moving objects is usually much cheaper then copying them e.g heap-based STL containers
- For some types e.g
std::arrayandstd::string(with SSO), copying them can be just as efficient
Item 30: Be aware of failure cases of perfect forwarding
- Perf-forwarding fails when template type deduction fails or deduces wrong type for the arg passed
- Fail cases: braced initializers and passing
0orNULL(instead ofnullptr) for null pointers - For integral
static constdata members, perfect-forwarding will fail if you're missing their definitions - For overloaded or
templatefunctions, avoid fail cases usingstatic_castto your desired type - Don't pass bitfields directly to perfect-forwarding functions; use
static_castto an lvalue first
Chapter 6. Lambda Expressions
Item 31: Avoid default capture modes
- Avoid default
&or=captures for lambdas because they can easily lead to dangling references - Fail cases:
&when they outlive the objects captured,=for member types when they outlivethis statictypes are always captured by-reference even though default capture mode could be by-value
Item 32: Use init-capture (aka generalized lambda captures) to move objects into (lambda) closures
- Init-capture allows you to initialize types (e.g variables) inside a lambda capture expression
Item 33: Use decltype on auto&& parameters for std::forward
- Use
decltypeonauto&¶meters when usingstd::forwardfor forwarding them to other functions - This case will typically occur when you are implementing perfect-forwarding using auto type deduction
Item 34: Prefer lambdas to std::bind
- Always prefer init capture based
lambdas(aka generalized lambdas) instead of usingstd::bind
Chapter 7. Concurrency API
Item 35: Prefer std::async (i.e task-based programming) to std::thread (i.e thread-based)
- When using
std::threads, you almost always need to handle scheduling and oversubscription issues - Using
std::async(aka task) with default launch policy handles most of the corner cases for you
Item 36: Specify std::launch::async for truly asynchronous tasks
std::async's default launch policy can run either async (in new thread) or sync (upon.get()call)- If you get
std::future_status::deferredon.wait_for(), call.get()to run the given task
Item 37: Always make std::threads unjoinable on all paths
- Avoid program termination by calling
.join()or.detach()on anstd::threadbefore it destructs! - Calling
.join()can lead to performance anomalies while.detach()leads to undefined behavior
Item 38: Be aware of varying destructor behavior of thread handle
std::futureblocks in destructor if policy isstd::launch::asyncby calling an implicit joinstd::shared_futureblocks when, additionally, the given shared future is the last copy in scopestd::packaged_taskdoesn't need a destructor policy but the underlyingstd::thread(running it) does
Item 39: Consider std::futures of void type for one-shot communication (comm.)
- For simple comm.,
std::condition_variable,std::mutexandstd::lock_guardis an overkill - Use
std::future<void>andstd::promisefor one-time communication between two threads
Item 40: Use std::atomic for concurrency and volatile for special memory
- Use
std::atomicguarantees thread-safety for shared memory whilevolatilespecifies special memory std::atomicprevents reordering of reads/write operations but permits elimination of redundant reads/writesvolatilespecifies special memory (e.g for memory mapped variables) which permits redundant reads/writes
Chapter 8. Tweaks
Item 41: When to use pass-by-value for functions parameters
- Consider pass-by-value for parameters if and only if they are always copied and are cheap to move
- Prefer
rvalue-refparameters for move-only types to limit copying to exactly one move operation - Never use pass-by-value for base class parameter types because it leads to the slicing problem
Item 42: Choose emplacement instead of insertion
- Use
.emplaceversions instead of.push/.insertto avoid temp copies when adding to STL containers - When value being added uses assignment,
.push/.insertwork just as well as the.emplaceversions - For containers of resource-managing types e.g smart pointers,
.push/.insertcan prevent memory leaks - Be careful when using
.emplacefunctions because the args passed can invoke explicit constructors