- 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.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
x
invoid 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) { ... }
- 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
andvolatile
are ignored if present in the ParamType - Raw arrays
[]
and function types always decay to pointer types unless they initialize references
auto
plays the role ofT
while its type specifier (i.e includingconst
and/or ref) as ParamType- For a braced initializer e.g
{1, 2, 3}
,auto
always deducesstd::initializer_list
as its type - Corner case:
auto
as a callablereturn
type uses template type deduction, not auto type deduction
decltype
, typically used in functiontemplate
s, determines a variable or an expression's typedecltype(auto)
, unlikeauto
, includes ref-ness when used in thereturn
type of a callable- Corner case:
decltype
on lvalue expression (except lvalue-names) yields lvalue-refs not lvalues
- 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
auto
prevents uninitialized variables and verbose declarations (e.gstd::unordered_map<T>::key_type
)- Use
auto
especially when declaring lambdas to directly hold closures unlikestd::function
- Use
auto
withstatic_cast
(a.k.a explicitly typed initializer idiom) to enforce correct types - Never use
auto
directly with invisible proxy classes such asstd::vector<bool>::reference
- 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 10int
s initialized to 20.
- Don't use
0
orNULL
, usenullptr
of typenullptr_t
which represents pointers of all types!
- Alias declarations (declared with
using
keyword) support templatization whiletypedefs
don't - Alias declarations avoid 1)
::type
suffix 2)typename
prefix when referring to other typedefs
- Use
enum class
instead ofenum
to limit scope of anenum
members to just inside theenum
enum class
es useint
by default, prevent implicit conversions and permit forward declarations
- Always make unwanted functions (such as copy-operations for move-only types)
public
anddelete
- Declare overriding functions in derived types
override
; usefinal
to prevent further inheritance
- Prefer
const_iterators
toiterators
for all STL containers e.gcbegin
instead ofbegin
- For max generic code, don't assume the existence of member
cbegin
; usestd::begin
instead
- 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
constexpr
objects are alwaysconst
and usable in compile-time evaluations e.gtemplate
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
- Make member functions of a type
const
as well asthread-safe
if they do not modify its members - For synchronization issues, consider
std::atomic
first and then move tostd::mutex
if required
- 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
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 returnstd::unique_ptr
std::array
,std::vector
andstd::string
are generally better choices than using raw arrays[]
std::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 thanstd::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
tothis
, always inherit your class type fromstd::enable_shared_from_this
std::weak_ptr
operates with the possibility that the object it points to might have been destroyedstd::weak_ptr::lock()
returns astd::shared_ptr
, but anullptr
for destroyed objects onlystd::weak_ptr
is typically used for caching, observer lists and prevention of shared pointers cycles
- 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
whenstd::weak_ptr
s outlive theirstd::shared_ptr
s to avoid memory de-allocation delays
- 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
- 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
std::move
performs an unconditional cast on lvalues to rvalues; you can then perform move opsstd::forward
casts its input arg to an rvalue only if the arg is bound to an rvalue name
- 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
- Universal references are usually a better choice than overloading functions for lvalues and rvalues
- Apply
std::move
on rvalue refs andstd::forward
on universal-refs last time each is used - Similarly, also apply
std::move
orstd::forward
accordingly when returning by value from functions - Never return local objects from functions with
std::move
! It can prevent return value optimization (RVO)
- 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
- 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
andstd::decay_t
work well for universal-refs and they read nicely - Universal-refs offer efficiency advantages although they sometimes suffer from usability disadvantages
- Reference collapsing converts
& &&
to&
(i.e lvalue ref) and&& &&
to&&
(i.e rvalue ref) - Reference collapsing occurs in
template
andauto
type deductions, alias declarations anddecltype
- Generally, moving objects is usually much cheaper then copying them e.g heap-based STL containers
- For some types e.g
std::array
andstd::string
(with SSO), copying them can be just as efficient
- Perf-forwarding fails when template type deduction fails or deduces wrong type for the arg passed
- Fail cases: braced initializers and passing
0
orNULL
(instead ofnullptr
) 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 usingstatic_cast
to your desired type - Don't pass bitfields directly to perfect-forwarding functions; use
static_cast
to an lvalue first
- 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
static
types are always captured by-reference even though default capture mode could be by-value
- Init-capture allows you to initialize types (e.g variables) inside a lambda capture expression
- Use
decltype
onauto&&
parameters when usingstd::forward
for forwarding them to other functions - This case will typically occur when you are implementing perfect-forwarding using auto type deduction
- Always prefer init capture based
lambdas
(aka generalized lambdas) instead of usingstd::bind
- 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
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
- Avoid program termination by calling
.join()
or.detach()
on anstd::thread
before it destructs! - Calling
.join()
can lead to performance anomalies while.detach()
leads to undefined behavior
std::future
blocks in destructor if policy isstd::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 underlyingstd::thread
(running it) does
- For simple comm.,
std::condition_variable
,std::mutex
andstd::lock_guard
is an overkill - Use
std::future<void>
andstd::promise
for one-time communication between two threads
- Use
std::atomic
guarantees thread-safety for shared memory whilevolatile
specifies special memory std::atomic
prevents reordering of reads/write operations but permits elimination of redundant reads/writesvolatile
specifies special memory (e.g for memory mapped variables) which permits redundant reads/writes
- 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
- 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