Repository files navigation Effective Modern C++ Cheatsheet
ref(s) : reference(s)
op(s) : operation(s)
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.g auto& lvalue_ref = x;
rvalue-ref(erence) : reference to an rvalue type typically denoted by && e.g auto&& 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 x in void foo(int x);
callable objects : objects supporting member operator() e.g functions , lambdas, std::function etc
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 , const and volatile are 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
auto plays the role of T while its type specifier (i.e including const and/or ref ) as ParamType
For a braced initializer e.g {1, 2, 3}, auto always deduces std::initializer_list as its type
Corner case : auto as a callable return type uses template type deduction , not auto type deduction
Item 3: Understand decltype
decltype, typically used in function templates, determines a variable or an expression's type
decltype(auto), unlike auto, includes ref-ness when used in the return type of a callable
Corner case : decltype on 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 (and typeid()) depends upon compiler; use Boost.TypeIndex library instead
Item 5: Prefer auto declarations
auto prevents uninitialized variables and verbose declarations (e.g std::unordered_map<T>::key_type)
Use auto especially when declaring lambdas to directly hold closures unlike std::function
Item 6: How to fix undesired auto type deduction?
Use auto with static_cast (a.k.a explicitly typed initializer idiom ) to enforce correct types
Never use auto directly with invisible proxy classes such as std::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_list version is always preferred for {} types
Corner case : std::vector<int> v{10, 20} creates a vector with 10 and 20, not 10 ints initialized to 20.
Item 8: Prefer nullptr to 0 and NULL
Don't use 0 or NULL, use nullptr of type nullptr_t which represents pointers of all types!
Item 9: Prefer alias declarations to typedefs
Alias declarations (declared with using keyword) support templatization while typedefs don't
Alias declarations avoid 1) ::type suffix 2) typename prefix when referring to other typedefs
Item 10: Prefer scoped enums to unscoped enums
Use enum class instead of enum to limit scope of an enum members to just inside the enum
enum classes use int by 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) public and delete
Item 12: Always declare overriding functions override
Declare overriding functions in derived types override; use final to prevent further inheritance
Item 13: Always prefer const_iterators to iterators
Prefer const_iterators to iterators for all STL containers e.g cbegin instead of begin
For max generic code, don't assume the existence of member cbegin; use std::begin instead
Item 14: Declare functions noexcept if they won't emit exceptions
Declare functions noexcept when they don't emit exceptions such as functions with wide contracts
Always use noexcept for move-operations, swap functions and memory allocation/deallocation
When a noexcept function emits an exception: stack is possibly wound and program is terminated
Item 15: Use constexpr whenever possible
constexpr objects are always const and usable in compile-time evaluations e.g template parameters
constexpr functions produce results at compile-time only if all of their args are known at compile-time
constexpr objects 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 const as well as thread-safe if they do not modify its members
For synchronization issues, consider std::atomic first and then move to std::mutex if 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_ptr owns what it points to, is fast as raw pointer (*) and supports custom deleters
Conversion to a std::shared_ptr is easy, therefore factory functions should always return std::unique_ptr
std::array, std::vector and std::string are generally better choices than using raw arrays []
Item 19: Use std::shared_ptr for shared-ownership resource management
std::shared_ptr points to an object with shared ownership but doesn't actually own the object
std::shared_ptr stores/updates metadata on heap and can be up to 2x slower than std::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_ptr to this, always inherit your class type from std::enable_shared_from_this
Item 20: Use std::weak_ptr for std::shared_ptr-like pointers that can dangle
std::weak_ptr operates with the possibility that the object it points to might have been destroyed
std::weak_ptr::lock() returns a std::shared_ptr, but a nullptr for destroyed objects only
std::weak_ptr is 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 new when 1) specifying custom deleters 2) pointed-to object is a braced initializer
Use new when std::weak_ptrs outlive their std::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::move performs an unconditional cast on lvalues to rvalues ; you can then perform move ops
std::forward casts 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&& and auto&&) 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::move on rvalue refs and std::forward on universal-refs last time each is used
Similarly, also apply std::move or std::forward accordingly 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-const types
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_t and std::decay_t work 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 template and auto type deductions, alias declarations and decltype
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::array and std::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 0 or NULL (instead of nullptr) for null pointers
For integral static const data members, perfect-forwarding will fail if you're missing their definitions
For overloaded or template functions, avoid fail cases using static_cast to your desired type
Don't pass bitfields directly to perfect-forwarding functions; use static_cast to 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 outlive this
static types 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 decltype on auto&& parameters when using std::forward for 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 using std::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::deferred on .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 an std::thread before 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::future blocks in destructor if policy is std::launch::async by calling an implicit join
std::shared_future blocks when, additionally, the given shared future is the last copy in scope
std::packaged_task doesn't need a destructor policy but the underlying std::thread (running it) does
Item 39: Consider std::futures of void type for one-shot communication (comm.)
For simple comm., std::condition_variable, std::mutex and std::lock_guard is an overkill
Use std::future<void> and std::promise for one-time communication between two threads
Item 40: Use std::atomic for concurrency and volatile for special memory
Use std::atomic guarantees thread-safety for shared memory while volatile specifies special memory
std::atomic prevents reordering of reads/write operations but permits elimination of redundant reads/writes
volatile specifies special memory (e.g for memory mapped variables ) which permits redundant reads/writes
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-ref parameters 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 .emplace versions instead of .push/.insert to avoid temp copies when adding to STL containers
When value being added uses assignment, .push/.insert work just as well as the .emplace versions
For containers of resource-managing types e.g smart pointers, .push/.insert can prevent memory leaks
Be careful when using .emplace functions because the args passed can invoke explicit constructors
About
Cheatsheet for best practices of Modern C++ (taken from Effective Modern C++)
Resources
License
Stars
Watchers
Forks
You can’t perform that action at this time.