- 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
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) { ... }
- 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
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
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
- 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
autoprevents uninitialized variables and verbose declarations (e.gstd::unordered_map<T>::key_type)- Use
autoespecially when declaring lambdas to directly hold closures unlikestd::function
- 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
- 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.
- Don't use
0orNULL, usenullptrof typenullptr_twhich represents pointers of all types!
- Alias declarations (declared with
usingkeyword) support templatization whiletypedefsdon't - Alias declarations avoid 1)
::typesuffix 2)typenameprefix when referring to other typedefs
- Use
enum classinstead ofenumto limit scope of anenummembers to just inside theenum enum classes useintby default, prevent implicit conversions and permit forward declarations
- Always make unwanted functions (such as copy-operations for move-only types)
publicanddelete
- Declare overriding functions in derived types
override; usefinalto prevent further inheritance
- Prefer
const_iteratorstoiteratorsfor all STL containers e.gcbegininstead ofbegin - For max generic code, don't assume the existence of member
cbegin; usestd::begininstead
- 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
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
- 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
- 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_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[]
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
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
- 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
- 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::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
- 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::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)
- 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
- 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
- Reference collapsing converts
& &&to&(i.e lvalue ref) and&& &&to&&(i.e rvalue ref) - Reference collapsing occurs in
templateandautotype 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::arrayandstd::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
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
- 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
- Init-capture allows you to initialize types (e.g variables) inside a lambda capture expression
- 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
- Always prefer init capture based
lambdas(aka generalized lambdas) instead of usingstd::bind
- 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
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
- 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
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
- For simple comm.,
std::condition_variable,std::mutexandstd::lock_guardis an overkill - Use
std::future<void>andstd::promisefor one-time communication between two threads
- 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
- 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
- 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