Cheatsheet for best practices of Modern C++ (taken from Effective Modern C++)
auto x = 10;
)10
)&
e.g auto& lvalue_ref = x;
&&
e.g auto&& rvalue_ref = 10;
x
in void foo(int x);
operator()
e.g functions, lambda
s, std::function
etcclass Widget;
, void foo(int x);
class Widget { ... };
, void foo(int x) { ... }
template
type deductionconst
and volatile
are ignored if present in the ParamType[]
and function types always decay to pointer types unless they initialize referencesauto
type deductionauto
plays the role of T
while its type specifier (i.e including const
and/or ref) as ParamType{1, 2, 3}
, auto
always deduces std::initializer_list
as its typeauto
as a callable return
type uses template type deduction, not auto type deduction
decltype
decltype
, typically used in function template
s, determines a variable or an expression's typedecltype(auto)
, unlike auto
, includes ref-ness when used in the return
type of a callable
decltype
on lvalue expression (except lvalue-names) yields lvalue-refs not lvalues
std::type_info::name
(and typeid()
) depends upon compiler; use Boost.TypeIndex library insteadauto
auto
declarationsauto
prevents uninitialized variables and verbose declarations (e.g std::unordered_map<T>::key_type
)auto
especially when declaring lambdas to directly hold closures unlike std::function
auto
type deduction?auto
with static_cast
(a.k.a explicitly typed initializer idiom) to enforce correct typesauto
directly with invisible proxy classes such as std::vector<bool>::reference
{}
prevents narrowing conversions and most vexing parse while ()
doesn'tstd::initializer_list
version is always preferred for {}
typesstd::vector<int> v{10, 20}
creates a vector with 10 and 20, not 10 int
s initialized to 20.nullptr
to 0
and NULL
0
or NULL
, use nullptr
of type nullptr_t
which represents pointers of all types!typedefs
using
keyword) support templatization while typedefs
don't::type
suffix 2) typename
prefix when referring to other typedefsenums
to unscoped enums
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 declarationspublic
and delete
override
override
; use final
to prevent further inheritanceconst_iterators
to iterators
const_iterators
to iterators
for all STL containers e.g cbegin
instead of begin
cbegin
; use std::begin
insteadnoexcept
if they won't emit exceptionsnoexcept
when they don't emit exceptions such as functions with wide contracts
noexcept
for move-operations, swap
functions and memory allocation/deallocationnoexcept
function emits an exception: stack is possibly wound and program is terminatedconstexpr
whenever possibleconstexpr
objects are always const
and usable in compile-time evaluations e.g template
parametersconstexpr
functions produce results at compile-time only if all of their args are known at compile-timeconstexpr
objects and functions can be used in a wider context i.e compile-time as well as runtime
const
member functions thread-safeconst
as well as thread-safe
if they do not modify its membersstd::atomic
first and then move to std::mutex
if requiredstd::unique_ptr
for exclusive-ownership of resource managementstd::unique_ptr
owns what it points to, is fast as raw pointer (*
) and supports custom deletersstd::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 []
std::shared_ptr
for shared-ownership resource managementstd::shared_ptr
points to an object with shared ownership but doesn't actually own the objectstd::shared_ptr
stores/updates metadata on heap and can be up to 2x slower than std::unique_ptr
std::make_shared<T>
for creating shared pointersstd::shared_ptr
s from a single raw pointer; it leads to undefined behavior
std::shared_ptr
to this
, always inherit your class type from std::enable_shared_from_this
std::weak_ptr
for std::shared_ptr
-like pointers that can danglestd::weak_ptr
operates with the possibility that the object it points to might have been destroyedstd::weak_ptr::lock()
returns a std::shared_ptr
, but a nullptr
for destroyed objects onlystd::weak_ptr
is typically used for caching, observer lists and prevention of shared pointers cycles
std::make_unique
and std::make_shared
) to direct use of newnew
(in cases below), prevent memory leaks by immediately passing it to a smart pointer!new
when 1) specifying custom deleters 2) pointed-to object is a braced initializer
new
when std::weak_ptr
s outlive their std::shared_ptr
s to avoid memory de-allocation delays
struct Impl
) and stores a pointer to itstd::unique_ptr<Impl>
and always implement your destructor and copy/move ops in an impl filestd::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 nameT&&
and auto&&
) always cast lvalues to lvalue-refs and rvalues to rvalue-refs
const
std::move
and std::forward
std::move
on rvalue refs and std::forward
on universal-refs last time each is usedstd::move
or std::forward
accordingly when returning by value from functionsstd::move
! It can prevent return value optimization (RVO)
const
typesstd::is_integral
) to aid in matchingstd::enable_if_t
and std::decay_t
work well for universal-refs and they read nicely& &&
to &
(i.e lvalue ref) and && &&
to &&
(i.e rvalue ref)template
and auto
type deductions, alias declarations and decltype
std::array
and std::string
(with SSO), copying them can be just as efficient0
or NULL
(instead of nullptr
) for null pointers
static const
data members, perfect-forwarding will fail if you're missing their definitionstemplate
functions, avoid fail cases using static_cast
to your desired typestatic_cast
to an lvalue first&
or =
captures for lambdas because they can easily lead to dangling references
&
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
decltype
on auto&&
parameters for std::forward
decltype
on auto&&
parameters when using std::forward
for forwarding them to other functionsstd::bind
lambdas
(aka generalized lambdas) instead of using std::bind
std::async
(i.e task-based programming) to std::thread
(i.e thread-based)std::thread
s, you almost always need to handle scheduling and oversubscription issuesstd::async
(aka task) with default launch policy handles most of the corner cases for youstd::launch::async
for truly asynchronous tasksstd::async
's default launch policy can run either async (in new thread) or sync (upon .get()
call)std::future_status::deferred
on .wait_for()
, call .get()
to run the given taskstd::thread
s unjoinable on all paths.join()
or .detach()
on an std::thread
before it destructs!.join()
can lead to performance anomalies while .detach()
leads to undefined behavior
std::future
blocks in destructor if policy is std::launch::async
by calling an implicit joinstd::shared_future
blocks when, additionally, the given shared future is the last copy in scopestd::packaged_task
doesn't need a destructor policy but the underlying std::thread
(running it) doesstd::future
s of void type for one-shot communication (comm.)std::condition_variable
, std::mutex
and std::lock_guard
is an overkillstd::future<void>
and std::promise
for one-time communication between two threadsstd::atomic
for concurrency and volatile
for special memorystd::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
rvalue-ref
parameters for move-only types to limit copying to exactly one move operation.emplace
versions instead of .push/.insert
to avoid temp copies when adding to STL containers.push/.insert
work just as well as the .emplace
versions.push/.insert
can prevent memory leaks.emplace
functions because the args passed can invoke explicit constructors