View Code? Open in Web Editor
NEW
This project forked from muqsitnawaz/modern-cpp-cheatsheet
Cheatsheet for best practices of Modern C++ (taken from Effective Modern C++)
License: MIT License
modern-cpp-cheatsheet's Introduction
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, lambda
s, 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 template
s, 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 int
s 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 class
es 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_ptr
s 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_ptr
s outlive their std::shared_ptr
s 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::thread
s, 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::thread
s 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::future
s 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
modern-cpp-cheatsheet's People
Contributors
Stargazers