effective modern c++

88
Effective Modern C++ Kai

Upload: wang-hsiangkai

Post on 14-Feb-2017

196 views

Category:

Software


2 download

TRANSCRIPT

Page 1: Effective Modern C++

Effective Modern C++ Kai

Page 2: Effective Modern C++

Template Type Deduction

• The Type deduction for T is dependent not just on the type of expr, but also on the form of ParamType.

• ParamType is a pointer or reference type.

• ParamType is a universal reference.

• ParamType is neither a pointer nor a reference.

template<typename T>void f(ParamType param);

f(expr)

Page 3: Effective Modern C++

ParamType is a pointer or reference type

1. If expr’s type is a reference, ignore the reference part.

2. Then pattern-match expr’s type against ParamType to determine T.

template<typename T>void f(T& param);

int x = 27;const int cx = x;const int& rx = x;

f(x); // T is int, param’s type is int&f(cx); // T is const int, param’s type is const int&f(rx); // T is const int, param’s type is const int&

Page 4: Effective Modern C++

ParamType is a universal reference• If expr is an lvalue, both T and ParamType are deduced to be lvalue references.

• It’s the only situation in template type deduction where T is deduced to be a reference.

• ParamType is declared using the syntax for an rvalue reference, its deduced type is an lvalue reference.

• If expr is an rvalue, the Case 1 rules apply.

template<typename T>void f(T&& param);

int x = 27;const int cx = x;const int& rx = x;

f(x); // T is int&, param’s type is int&f(cx); // T is const int&, param’s type is const int&f(rx); // T is const int&, param’s type is const int&f(27); // T is int, param’s type is int&&

Page 5: Effective Modern C++

ParamType is neither a pointer nor a reference

template<typename T>void f(T param);

int x = 27;const int cx = x;const int& rx = x;

f(x); // T is int, param’s type is intf(cx); // T is int, param’s type is intf(rx); // T is int, param’s type is int

• Param will be a copy of whatever is passed in.

1. If expr’s type is a reference, ignore the reference part.

2. If expr is const, ignore that. If it’s volatile, also ignore that.

Page 6: Effective Modern C++

Array Argumentstemplate<typename T>void f(T param);

// The type of an array passed to a template function by value is deduced// to be a pointer type.const char name[] = “J. P. Briggs”;f(name); // T deduced as const char * // Arrays decay into pointer.

template<typename T>void f(T& param); // passed to a template function by reference

f(name); // T deduced to be const char [13] // Param’s type is const char (&)[13]

Page 7: Effective Modern C++

Array Arguments

template<typename T, std::size_t N>constexpr std::size_t arraySize(T (&)[N]) noexcept{ return N;}

int keyVals[] = {1, 3, 7, 9, 11, 22, 35};int mappedVals[arraySize(keyVals)];std::array<int, arraySize(keyVals)> mappedVals;

• The ability to declare references to array enables creation of a template that deduces the number of elements that an array contains.

Page 8: Effective Modern C++

Function Arguments

void someFunc(int, double); // type is void (int, double)

template<typename T>void f1(T param); // pass by value

template<typename T>void f2(T& param); // pass by reference

f1(someFunc); // param’s type is void (*)(int, double) // Function types decay into function pointers.

f2(someFunc); // param’s type is void (&)(int, double)

Page 9: Effective Modern C++

auto type deduction

auto x = 27;

const auto cx = x;

const auto& rx = x;

template<typename T>void x(T param);x(27)

template<typename T>void cx(const T param);cx(x);

template<typename T>void rx(const T& param);rx(x);

With only one exception, auto type deduction is template type deduction.

1. The type specifier is a pointer or reference, but not a universal reference. 2. The type specifier is a universal reference. 3. The type specifier is neither a pointer nor a reference.

Page 10: Effective Modern C++

auto type deduction

auto x1 = 27; // intauto x2(27); // intauto x3 = {27}; // std::initializer_list<int>auto x4{27}; // std::initializer_list<int>

When the initialiser for an auto-declared variable is enclosed in braces, the deduced type is a std::initializer_lists.

auto x = {11, 23, 9};template<typename T>void x(T param);

x({11, 23, 9}); // error! can’t deduce type

auto assumes that a braced initialiser represents a std::initializer_list, but template type deduction doesn’t.

template<typename T>void x(std::initializer_list<T> param);

x({11, 23, 9}); // T deduced as int, param’s type is std::initializer_list<int>

Page 11: Effective Modern C++

auto type deductionC++14: 1. permit auto to indicate that a function’s return type should be deduced. 2. lambdas may use auto in parameter declarations.

these use of auto employ template type deduction, not auto type deduction.

auto createInitList(){ return {1, 2, 3}; // error: can’t deduce type for {1, 2, 3} }

Page 12: Effective Modern C++

• Given a name or an expression, decltype tells you the name’s or the expression’s type.

• The primary use for decltype is declaring function templates where the function’s return type depends on its parameter types.

decltype

// C++11template<typename Container, typename Index>auto autoAndAccess(Container& c, Index i) -> decltype(c[i]){ authenticateUser(); return c[i]; }

// C++14template<typename Container, typename Index>auto autoAndAccess(Container& c, Index i){ authenticateUser(); return c[i]; // return type deduced from c[i] }

Page 13: Effective Modern C++

• To prevent to remove reference-ness of an initialising expression. (Rule 3 in template type deduction.)

decltype

// C++14template<typename Container, typename Index>decltype(auto) autoAndAccess(Container& c, Index i){ authenticateUser(); return c[i]; // return type deduced from c[i] }

std::deque<int> d;authAndAccess(d, 5); // d[5] returns an int&

Page 14: Effective Modern C++

decltype

// C++14Widget w;const Widget& cw = w;

auto myWidget1 = cw; // myWidget1’s type is Widget // const-ness and reference-ness will be removed

decltype(auto) myWidget2 = cw; // myWidget2’s type is const Widget&

Page 15: Effective Modern C++

decltype// Accept left-reference and right-reference container// C++11template<typename Container, typename Index>autoauthAndAccess(Container&& c, Index i) -> decltype(std::forward<Container>(c)[i]){ authenticateUser(); return std::forward<Container>(c)[i];}

// C++14template<typename Container, typename Index>decltype(auto)authAndAccess(Container&& c, Index i){ authenticateUser(); return std::forward<Container>(c)[i];}

Page 16: Effective Modern C++

decltype• Applying decltype to a name yields the declared type for that name.

• For lvalue expressions more complicated than names, decltype ensures that the type reported is always an lvalue reference.// C++14decltype(auto) f1(){ int x = 0; … return x; // decltype(x) is int, so f1 returns int}

decltype(auto) f2(){ int x = 0; … return (x); // decltype((x)) is int&, so f2 returns int& // return a reference to a local variable! (undefined behavior)}

Page 17: Effective Modern C++

auto - must be initialised• auto variables have their type deduced from their initialiser, so they

must be initialised.

int x1; // potentially uninitialisedauto x2; // error! initialiser requiredauto x3 = 0; // fine, x’s value is well-defined

Page 18: Effective Modern C++

auto - avoid verbose

template<typename It>void dwim(It b, It e){ while (b != e) { typename std::iterator_traits<It>::value_type currValue = *b; … }}

template<typename It>void dwim(It b, It e){ while (b != e) { auto currValue = *b; … }}

• avoid verbose variable declarations

Page 19: Effective Modern C++

auto - hold a closure• std::function is a template in the C++11 Standard Library that

generalised the idea of a function pointer. • Using std::function is not the same as using auto.

• An auto-declared variable holding a closure has the same type asthe closure, it uses only as much memory as the closure requires.

std::function<bool(const std::unique_ptr<Widget>&, const std::unique_ptr<Widget>&)> derefUPLess = [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2) { return *p1 < *p2; };

// C++11auto derefUPLess = [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2) { return *p1 < *p2; };

// C++14auto derefUPLess = [](const auto& p1, const auto& p2) { return *p1 < *p2; };

Page 20: Effective Modern C++

auto• The type of a std::function-declared variable holding a closure is an

instantiation of the std::function template, and that has a fixed sizefor any given signature.

• The std::function approach is generally bigger and slower than theauto approach, and it may yield out-of-memory exceptions.

std::function<bool(const std::unique_ptr<Widget>&, const std::unique_ptr<Widget>&)> derefUPLess = [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2) { return *p1 < *p2; };

// C++11auto derefUPLess = [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2) { return *p1 < *p2; };

// C++14auto derefUPLess = [](const auto& p1, const auto& p2) { return *p1 < *p2; };

Page 21: Effective Modern C++

auto• Use auto to avoid potential bugs and performance issues.std::vector<int> v;unsigned sz = v.size(); // potential bugs. unsigned is 32-bits on 64-bits machine // v.size()’s type is std::vector<int>::size_type

auto sz = v.size(); // sz’s type is std::vector<int>::size_type

std::unordered_map<std::string, int> m;// Key value is const, so actual type is std::pair<const std::string, int>// There will be a temporary variable of std::pair<std::string, int>// Every loop has an extra constructor and destructorfor (const std::pair<std::string, int>& p : m) { …}

// avoid temporary variable// simply bind the reference p to each element in mfor (const auto& p : m) { …}

Page 22: Effective Modern C++

auto• operator[] for std::vector<bool> doesn’t return a reference to

an element of the container. C++ forbids references to bits. • It returns an object of type std::vector<bool>::reference

std::vector<bool> features(const Widget& w);

Widget w;auto highPriority = features(w)[5]; // deduced type is std::vector<bool>::reference // highPriority is a reference to a temporary // variable. It is a dangling pointer.processWidget(w, highPriority); // undefined behaviour!

std::vector<bool> features(const Widget& w);

Widget w;bool highPriority = features(w)[5]; // std::vector<bool>::reference implicit // convert to boolprocessWidget(w, highPriority);

Page 23: Effective Modern C++

auto• std::vector<bool>::reference is a proxy class that exists for the

purpose of emulating and augmenting the behaviour of some other type. • As a general rule, proxy classes don’t play well with auto. • Objects of such classes are often not designed to live longer than a

single statement.

// solutionstd::vector<bool> features(const Widget& w);

Widget w;auto highPriority = static_cast<bool>(features(w)[5]); // explicitly typedprocessWidget(w, highPriority);

// avoid thisauto someVar = expression of proxy class type

Page 24: Effective Modern C++

Brace Initialiser• There are three methods to initialise variables.

int x(0); // initialiser is in parenthesesint y = 0; // initialiser follows “=“int z{ 0 }; // initialiser is in braces

• There were some situations where c++98 had no way to express a desired initialisation.

std::vector<int> v{1, 3, 5}; // (C++11) v’s initial content is 1, 3, 5

Page 25: Effective Modern C++

Brace Initialiser• Brace initialiser is universal initialiser.// C++11. Braces can be used to initialise non-static data members.class Widget { …private: int x{0}; // fine, x’s default value is 0 int y = 0; // also fine int z(0); // error!};

// uncopyable objects may be initialised using braces or parentheses.std::atomic<int> ai1{0}; // finestd::atomic<int> ai2(0); // finestd::atomic<int> ai3 = 0; // error!

Page 26: Effective Modern C++

Brace Initialiser• Brace initialiser prohibits implicit narrowing conversions.

double x, y, z;

int sum1{x + y + z}; // error! sum of doubles may not be expressible as intint sum2(x + y + z); // okayint sum3 = x + y + z; // okay

Page 27: Effective Modern C++

Brace Initialiser• std::initializer_list overshadows other constructors to the point

where the other overloads may hardly be considered.

class Widget {public: Widget(int i, bool b); Widget(int i, double d); Widget(std::initializer_list<long double> il); …};

Widget w1(10, true); // call first ctorWidget w2{10, true}; // call std::initializer_list ctorWidget w3(10, 5.0); // call second ctorWidget w4{10, 5.0}; // call std::initializer_list ctor

Page 28: Effective Modern C++

Brace Initialiser• Most developers end up choosing one kind of delimiter as a default, using

the other only when they have to.

Page 29: Effective Modern C++

nullptr• 0 and NULL are integral type, not pointer type. • nullptr’s type is std::nullptr_t.

The type implicitly converts to all raw pointer types.

void f(int);void f(bool);void f(void*);

f(0); // call f(int), not f(void*)

f(NULL); // might not compile, but typically calls f(int)

Page 30: Effective Modern C++

nullptr• improve code clarity

auto result = findRecord();

if (result == 0) { // the return value may be int or pointer …}

auto result = findRecord();

if (result == nullptr) { // the return value must be pointer, it is more clear. … }

Page 31: Effective Modern C++

nullptr• template type deduction

template<typename FuncType, typename MuxType, typename PtrType>auto lockAndCall(FuncType func, MuxType mutex, PtrType ptr) -> decltype(func(ptr)){ using MuxGuard = std::lock_guard<MuxType>;

MuxGuard g(mutex); // lock mutex return func(ptr); // call function } // unlock mutex

int f1(std::shared_ptr<Widget> spw); // call these only whendouble f2(std::unique_ptr<Widget> upw); // the appropriatebool f3(Widget* pw); // mutex is locked

std::mutex f1m, f2m, f3m;

auto result1 = lockAndCall(f1, f1m, 0); // error! ptr’s type is deduced to intauto result2 = lockAndCall(f2, f2m, NULL); // error!auto result3 = lockAndCall(f3, f3m, nullptr); // fine. ptr’s type is std::nullptr_t // implicitly convert to Widget*

Page 32: Effective Modern C++

Alias Declarations// use typedeftypedef std::unique_ptr<std::unordered_map<std::string, std::string>> UPtrMapSS;typedef void (*FP)(int, const std::string&);

// use alias declarationusing UPtrMapSS = std::unique_ptr<std::unordered_map<std::string, std::string>>using FP = void (*)(int, const std::string&);

Page 33: Effective Modern C++

Alias Declarations

// alias declarationtemplate<typename T>using MyAllocList = std::list<T, MyAlloc<T>>;

MyAllocList<Widget> lw;

// typedeftemplate<typename T>struct MyAllocList { typedef std::list<T, MyAlloc<T>> type;};

MyAllocList<Widget>::type lw;

• Alias declarations may be templatized, while typedefs cannot.

Page 34: Effective Modern C++

Alias Declarations

template<typename T>struct MyAllocList { typedef std::list<T, MyAlloc<T>> type;};

template<typename T>class Widget {private: typename MyAllocList<T>::type list; // MyAllocList<T> may be a specialization. // “type” may be a data member of it // The names of dependent types must // be preceded by typename …};

• typedef inside a template class

Page 35: Effective Modern C++

Scoped enum

enum Color { black, white, red }; // unscoped enumauto white = false; // error! white already declared in this scope

enum class Color { black, white, red }; // scoped enum. avoid name pollutionauto white = false; // fine.

• The name of unscoped enum belong to the scope containing the enum.

Page 36: Effective Modern C++

Scoped enum

enum class Color { black, white, red }; // scoped enum

std::vector<std::size_t> primeFactors(std::size_t x);

Color c = Color::white; // fine.if (c < 14.5) { // error! can’t convert to double auto factors = primeFactors(c); // error! can’t convert to std::size_t …}

• Scoped enum is strongly typed

Page 37: Effective Modern C++

Scoped enum

enum class Color; // fine.enum Color; // error!

• Scoped enum could be forward-declared

• If unscoped enum wants to be forward-declared, it needs to specifyunderlying type.

enum Color: std::uint8_t; // forward-declaration for unscoped enum

Page 38: Effective Modern C++

deleted function

// C++98template <class charT, class traits = char_traits<charT> >class basic_ios : public ios_base {public: …

private; basic_ios(const basic_ios& ); // not defined (link-time error) basic_ios& operator=(const basic_ios& ); // not defined}

• prevent calling some particular functions

// C++11template <class charT, class traits = char_traits<charT> >class basic_ios : public ios_base {public: // C++ checks accessibility before deleted status. // Let compiler show more clear messages. … basic_ios(const basic_ios& ) = delete; // if used, it’s a compile-time basic_ios& operator=(const basic_ios& ) = delete; // error. …}

Page 39: Effective Modern C++

deleted function

template<typename T>void processPointer(T* ptr);

template<>void processPointer<void>(void*) = delete;

template<>void processPointer<char>(char*) = delete;

• prevent use of template instantiations that should be disabled

// template member functionclass Widget {public: template<typename T> void processPointer(T* ptr) { … }};

template<>void Widget::processPointer<void>(void*) = delete;

Page 40: Effective Modern C++

override

class Base {public: virtual void mf1() const; virtual void mf2(int x); virtual void mf3() &;};

class Derived : public Base {public: virtual void mf1() const override; virtual void mf2(int x) override; virtual void mf3() & override;};

• make explicit that a derived class function is supposed to overridea base class version

Page 41: Effective Modern C++

Prefer const_iterators to iterators

std::vector<int> valuesauto it = std::find(values.cbegin(), values.cend(), 1983);values.insert(it, 1998);

• The standard practice of using const whenever possible dictates that youshould use const_iterators any time you need an iterator.

• There is no portable conversion from a const_iterator to an iterator,not even with a static_cast.

Page 42: Effective Modern C++

constexpr objects

int sz;constexpr auto arraySize1 = sz; // error! sz’s value not known at compilationstd::array<int, sz> data1; // error!

constexpr auto arraySize2 = 10;std::array<int, arraySize2> data2; // fine. arraySize2 is constexpr

int sz2;const auto arraySize = sz2; // fine. arraySize is const copy of sz2std::array<int, arraySize> data; // error! arraySize’s value not known at compilation

• constexpr indicates a value that’s not only constant, it’s knownduring compilation.

• constexpr objects are, in fact, const, and they do have values that areknown at compile time.

Page 43: Effective Modern C++

constexpr functions

constexprint pow(int base, int exp) noexcept{ …}

constexpr auto numConds = 5;std::array<int, pow(3, numConds)> results; // call pow function at compile-time

auto base = readFromDB(“base”);auto exp = readFromDB(“exponent”);auto baseToExp = pow(base, exp); // call pow function at runtime

• constexpr functions produce compile-time constants when they are calledwith compile-time constants. If they’re called with values not known untilruntime, they produce runtime values.

Page 44: Effective Modern C++

constexpr

// C++11constexpr int pow(int base, int exp) noexcept{ return (exp == 0 ? 1 : base * pow(base, exp - 1));}

// C++14constexpr int pow(int base, int exp) noexcept{ auto result = 1; for (int i = 0; i < exp; i++) result *= base;

return result; }

• In C++11, constexpr functions may contain no more than a singleexecutable statement: a return.

Page 45: Effective Modern C++

constexpr

class Point {public: constexpr Point(double xVal = 0, double yVal = 0) noexcept : x(xVal), y(yVal) {} constexpr double xValue() const noexcept { return x; } constexpr double yValue() const noexcept { return y; } void setX(double newX) noexcept { x = newX; } // setX could be constexpr in C++14 void setY(double newY) noexcept { y = newY; }private: double x, y;};

constexpr Point p1(9.4, 27.7); // “runs” constexpr actor during compilationconstexpr Point p2(28.8, 5.3);

constexpr Point midpoint(const Point& p1, const Point& p2) noexcept{ return { (p1.xValue() + p2.xValue()) / 2, (p1.yValue() + p2.yValue()) / 2};}

constexpr auto mid = midpoint(p1, p2);

• constructors and other member functions may be constexpr

Page 46: Effective Modern C++

make const member functions thread safe

• roots doesn’t change Polynomial object on which it operates, but aspart of its caching activity, it may need to modify rootVals androotsAreValid.

• roots is declared const, but it’s not thread safe.

class Polynomial {public: using RootsType = std::vector<double>; RootsType roots() const { if (!rootsAreValid) { … rootsAreValid = true; } return rootVals; }

private: mutable bool rootsAreValid{ false }; mutable RootsType rootVals{}; };

Page 47: Effective Modern C++

make const member functions thread safe• std::mutex is a move-only type. A side effect of adding m to Polynomial

is that Polynomial loses the ability to be copied.

class Polynomial {public: using RootsType = std::vector<double>; RootsType roots() const { std::lock_guard<std::mutex> g(m); if (!rootsAreValid) { … rootsAreValid = true; } return rootVals; }

private: mutable std::mutex m; mutable bool rootsAreValid{ false }; mutable RootsType rootVals{}; };

Page 48: Effective Modern C++

make const member functions thread safe• std::atomic variables are often less expensive than mutex. • For a single variable or memory location requiring synchronization, use

of a std::atomic is adequate.

class Point {public: double distanceFromOrigin() const noexcept { ++callCount; return std::sqrt((x * x) + (y * y)); };

private: mutable std::atomic<unsigned> callCount{ 0 }; double x, y;};

Page 49: Effective Modern C++

special member function generation• the default constructor • the destructor • the copy constructor • the copy assignment operator • the move constructor (C++11) • the move assignment operator (C++11)

class Widget {public: Widget(Widget&& rhs); // move constructor widget& operator=(Widget&& rhs); // move assignment operator};

Page 50: Effective Modern C++

special member function generation• the move constructor (C++11) • the move assignment operator (C++11)

• perform “memberwise moves” on the non-static data members • “memberwise moves” are more like member wise move requests. • memberwise move consists of move operations on data members

and base classes that support move operations, but a copy operationfor those that don’t.

• the two move operations are not independent. • declaring a move constructor prevents a move assignment operator

from being generated. • declaring a move assignment operator prevents compilers from

generating a move constructor. • move operations won’t be generated for any class that explicitly declares

a copy operation. • C++11 does not generate move operations for a class with a user-declared

destructor. • The Rule of Three: if you declare any of a copy constructor, copy

assignment operator, or destructor, you should declare all three.

Page 51: Effective Modern C++

special member function generation• So move operations are generated for classes only if these three things are

true: • No copy operations are declared in the class. • No move operations are declared in the class. • No destructor is declared in the class.

Page 52: Effective Modern C++

std::unique_ptr• std::unique_ptr embodies exclusive ownership semantic.

• Copying a std::unique_ptr isn’t allowed. • By default, that destruction would take place via delete, but, during

construction, std::unique_ptr objects can be configured to use customdeleters.

auto delInvmt = [](Investment* pInvestment) // accept a raw pointer as argument { makeLogEntry(pInvestment); delete pInvestment; };

template<typename… Ts>std::unique_ptr<Investment, decltype(delInvmt)> // specified in unique_ptrmakeInvestment(Ts&&… params){ std::unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr, delInvmt); if ( /* a Stock object should be created */ ) { pInv.reset(new Stock(std::forward<Ts>(params)…); } return pInv;}

Page 53: Effective Modern C++

std::unique_ptr• Deleters that are function pointers generally cause the size of astd::unique_ptr to grow from one word to two.

• For deleters that are function objects, the change in size depends onhow much state is stored in the function object.

• Stateless function objects (lambda expressions) incur no size penalty.

Page 54: Effective Modern C++

std::unique_ptr• std::unique_ptr comes in two forms, one for individual objects )std::unique_ptr<T>) and one for arrays (std::unique_ptr<T[]>).

• One of its most attractive features is that it easily and efficiently converts toa std::shared_ptr.

• std::shared_ptr can’t work with arrays.

Page 55: Effective Modern C++

std::shared_ptr• When the last std::shared_ptr pointing to an object stops pointing there,

that std::shared_ptr destroys the object it points to. • A std::shared_ptr can tell whether it’s the last one pointing to a resource

by consulting the resource’s reference count. • std::shared_ptr are twice the size of a raw pointer. • Memory for the reference count must be dynamically allocated. • Increments and decrements of the reference count must be atomic.

Page 56: Effective Modern C++

std::shared_ptr Custom Deleters• For std::unique_ptr, the type of the deleter is part of the type of the smart

pointer. For std::shared_ptr, it’s not. • Specifying a custom deleter doesn’t change the size of a std::shared_ptr

object.auto loggingDel = [](Widget* pw) { makeLogEntry(pw); delete pw; };

std::unique_ptr<Widget, decltype(loggingDel)> upw(new Widget, loggingDel);std::shared_ptr<Widget> spw(new Widget, loggingDel);

pointer to T

pointer to Contrl Block

std::shared_ptr<T>

T Object

Reference Count

Weak Count

Custom Deleter

Page 57: Effective Modern C++

std::shared_ptr Control Block• An object’s control block is set up by the function creating the firststd::shared_ptr to the object. • std::make_shared always creates a control block. • A control block is created when a std::shared_ptr is constructed from

a unique-ownership pointer. • When a std::shared_ptr constructor is called with a raw pointer, it

creates a control block.

auto pw = new Widget; // The creation of the raw pointer is bad.std::shared_ptr<Widget> spw1(pw, loggingDel); // create control blockstd::shared_ptr<Widget> spw2(pw, loggingDel); // create 2nd control block // It will cause undefined behavior.

std::shared_ptr<Widget> spw1(new Widget, loggingDel); // direct use of newstd::shared_ptr<Widget> spw2(spw1); // spw2 uses same control block as spw1

Page 58: Effective Modern C++

std::shared_ptrstd::vector<std::shared_ptr<Widget>> processedWidgets;

class Widget {public: void process(); …};

void Widget::process(){ processWidgets.emplace_back(this); // pass raw pointer to shared_ptr // This is wrong! // It may cause undefined behaviour. }

Page 59: Effective Modern C++

std::shared_ptrstd::vector<std::shared_ptr<Widget>> processedWidgets;

// The Curiously Recurring Template Pattern (CRTP)class Widget : public std::enable_shared_from_this<Widget> {public: // To prevent clients from calling member functions that invoke shared_from_this // before a std::shared_ptr points to the object. template<typename… Ts> static std::shared_ptr<Widget> create(Ts&&… params); // factory method … void process(); …private: … // private ctors};

void Widget::process(){ processWidgets.emplace_back(shared_from_this()); // There must be an existing // std::shared_ptr that points // to the current object. }

Page 60: Effective Modern C++

std::weak_ptr• A pointer like std::shared_ptr that doesn’t affect an object’s

reference count. • std::weak_ptr isn’t a standalone smart pointer. It’s an augmentation ofstd::shared_ptr.

auto spw = std::make_shared<Widget>();std::weak_ptr<Widget> wpw(spw); // wpw points to same Widget as spw.spw = nullptr; // wpw now dangles.if (wpw.expired()) … // if wpw doesn’t point to an object…

// “check then create” should be atomic.// creating a shared_ptr from the std::weak_ptr// 1. use weak_ptr::lockstd::shared_ptr<Widget> spw1 = wpw.lock(); // if wpw’s expired, spw1 is nullauto spw2 = wpw.lock(); // same as above, but uses auto// 2. use constructorstd::shared_ptr<Widget> spw3(wpw); // if wpw’s expired, throw std::bad_weak_ptr

Page 61: Effective Modern C++

std::weak_ptr• Caching • Observer design pattern

• keep a list of observers • Backward pointer

// caching examplestd::unique_ptr<const Widget> loadWidget(WidgetID id); // costly version

std::shared_ptr<const Widget> fastLoadWidget(WidgetID id) // caching version{ static std::unordered_map<WidgetID, std::weak_ptr<const Widget>> cache; auto objPtr = cache[id].lock(); if (!objPtr) { objPtr = loadWidget(id); // load the object cache[id] = objPtr; // cache it } return objPtr; }

A B Cstd::shared_ptr std::shared_ptr

std::weak_ptr

Page 62: Effective Modern C++

std::make_unique && std::make_shared• std::make_shared is part of C++11. • std::make_unique is part of C++14. • This form of the function doesn’t support arrays or custom deleters. • std::allocate_shared acts like std::make_shared, except

its first argument is an allocator object to be used for the dynamicmemory allocation.

// make_unique implementation for C++11template<typename T, typename… Ts>std::unique_ptr<T> make_unique(Ts&&… params){ return std::unique_ptr<T>(new T(std::forward<Ts>(params)…)); }

auto upw1(std::make_unique<Widget>()); // with make functionstd::unique_ptr<Widget> upw2(new Widget); // without make function

auto spw1(std::make_shared<Widget>()); // with make functionstd::shared_ptr<Widget> spw2(new Widget); // without make function

Page 63: Effective Modern C++

std::make_unique && std::make_sharedint computePriority();

// Before calling processWidget,// * The expression “new Widget” must be evaluated// * The constructor for the std::shared_ptr<Widget>// * computePriority must runprocessWidget(std::shared_ptr<Widget>(new Widget), computePriority());

// If compiler emits code as follow// 1. Perform “new Widget”// 2. Execute computePriority// 3. Run std::shared_ptr constructor// If (2) produces an exception, the dynamically allocated memory will be leaked.

// std::make_shared has no such problems.processWidget(std::make_shared<Widget>(), computePriority());

Page 64: Effective Modern C++

std::make_unique && std::make_shared// Two allocations.// 1. allocate memory for Widget// 2. allocate memory for control block of std::shared_ptrstd::shared_ptr<Widget> spw(new Widget);

// One allocation suffices.// Caveat: Widget memory will not free until no one uses control block.auto spw = std::make_shared<Widget>();

Page 65: Effective Modern C++

std::make_unique && std::make_shared• None of the make functions permit the specification of custom deleters. • Within the make functions, the perfect forwarding code uses parentheses,

not braces. • Using make functions to create objects of types with class-specific versions

of operator new and operator delete is typically a poor idea.

Page 66: Effective Modern C++

std::make_unique && std::make_shared• Exception safe code without make functions.void processWidget(std::shared_ptr<Widget> spw, int priority);void cusDel(Widget *ptr); // custom deleter

// Put allocation of the Widget and the construction of the std::shared_ptr// into one statement.std::shared_ptr<Widget> spw(new Widget, cusDel); // exception safeprocessWidget(std::move(spw), computePriority()); // use move() to turn lvalue // to rvalue. It is more // efficient.

// Because processWidget’s first parameter is passed by value, construction// from an rvalue entails only a move, while construction from an lvalue requires// a copy.

Page 67: Effective Modern C++

Pimpl Idiom// Original classclass Widget {public: Widget(); …private: std::string name; std::vector<double> data; Gadget g1, g2, g3;};

// Pimpl Idiomclass Widget {public: Widget(); ~Widget(); // door is needed

private: struct Impl; // declare implementation struct Impl *pImpl; // and pointer to it};

Page 68: Effective Modern C++

Pimpl Idiom (raw pointer)#include “widget.h” // in impl. file “widget.cpp”#include “gadget.h”#include <string>#include <vector>

struct Widget::Impl { std::string name; std::vector<double> data; Gadget g1, g2, g3;};

Widget::Widget(): pImpl (new Impl) {}

Widget::~Widget(){ delete pImpl; }

Page 69: Effective Modern C++

Pimpl Idiom (std::unique_ptr)

class Widget { // in header “widget.h”public: Widget(); ~Widget(); // declaration only

Widget(Widget&& rhs); // declaration only Widget& operator=(Widget&& rhs);

Widget(const Widget& rhs); // declaration only Widget& operator=(const Widget& rhs);

private: struct Impl; // incomplete type declaration std::unique_ptr<Impl> pImpl;};

Page 70: Effective Modern C++

Pimpl Idiom (std::unique_ptr)#include “widget.h” // in impl. file “widget.cpp”#include “gadget.h”#include <string>#include <vector>

struct Widget::Impl { std::string name; std::vector<double> data; Gadget g1, g2, g3;};

Widget::Widget(): pImpl(std::make_unique<Impl>()) {}

// dtor use static_assert to ensure that the// raw pointer is complete typeWidget::~Widget() = default; // destructor needs to see complete type

// generate code to destroy pImpl when exception arises// (need complete type)Widget::Widget(Widget&& rhs) = default;

// need to destroy the object pointed to by pImpl// before reassigning it. (need complete type)Widget& Widget::operator=(Widget&& rhs) = default;

Widget::Widget(const Widget& rhs) // deep copy: pImpl(std::make_unique<Impl>(*rhs.pImpl)) {}Widget& Widget::operator=(const Widget& rhs){ *pImpl = *rhs.pImpl; return *this; }

Page 71: Effective Modern C++

std::move

// C++11template<typename T>typename remove_reference<T>::type&& // ensuring that && is applied to a type that isn’t a reference.move(T&& param){ using ReturnType = typename remove_reference<T>::type&&;

return static_cast<ReturnType>(param);}

// C++14template<typename T>decltype(auto) move(T&& param){ using ReturnType = remove_reference_t<T>&&; return static_cast<ReturnType>(param); }

• std::move does nothing but cast its argument to an rvalue.

Page 72: Effective Modern C++

std::move

class Annotation {public: explicit Annotation(const std::string text) : value(std::move(text)) // move will convert to const std::string&& { … } // It will call copy ctor, not move ctor. …private: std::string value;};

class string {public: … string(const string& rhs); // copy ctor string(string&& rhs); // move ctor};

• Don’t declare objects const if you want to be able to move from them. • std::move doesn’t guarantee that the object it’s casting will be eligible

to be moved.

Page 73: Effective Modern C++

std::forward

void process(const Widget& lvalArg);void process(Widget&& rvalArg);

template<typename T>void logAndProcess(T&& param) // universal reference{ auto now = std::chrono::system_clock::now(); makeLogEntry(“Calling ‘process’”, now); process(std::forward<T>(param)); // T will convey rvalue or lvalue information.}

Widget w;logAndProcess(w); // call with lvaluelogAndProcess(std::move(w)); // call with rvalue

• std::forward casts to an rvalue only if its argument was initialised withan rvalue.

Page 74: Effective Modern C++

rvalue reference and universal reference

void f(Widget&& param); // rvalue reference

Widget&& var1 = Widget(); // rvalue reference

auto&& var2 = var1; // universal reference (auto declaration)

template<typename T>void f(std::vector<T>&& param); // rvalue reference

template<typename T>void f(T&& param); // universal reference (function template parameter)

template<typename T>void f(const T&& param); // rvalue reference

• Universal references arise in two context. • function template parameters (It must be precisely “T&&”.) • auto declarations

• What these contexts have in common is the presence of type deduction.

Page 75: Effective Modern C++

rvalue reference and universal referencetemplate<class T, class Allocator = allocator<T>>class vector {public: void push_back(T&& x); // rvalue reference template <class… Args> void emplace_back(Args&&… args); // universal reference …};

auto timeFuncInvocation = [](auto&& func, auto&&… params) // universal reference { // start timer; std::forward<decltype(func)>(func)( std::forward<decltype(params)>(params)…); // stop timer and record elapsed time; };

Page 76: Effective Modern C++

Use std::forward on universal reference// Example 1class Widget {public: template<typename T> void setName(T&& newName) // universal reference { name = std::move(newName); } // bad idea to use move() on universal reference …private: std::string name;};

std::string getWidgetName();Widget w;auto n = getWidgetName();w.setName(n); // move n into w // n’s value now unknown (potential error!)

// Example 2template<typename T>FractionreduceAndCopy(T&& frac){ frac.reduce(); return std::forward<T>(frac); // move rvalue into return value, copy lvalue }

Page 77: Effective Modern C++

Use std::move on rvalue referenceMatrixoperator+(Matrix&& lhs, const Matrix& rhs){ lhs += rhs; return std::move(lhs); // move lhs into return value (more efficient)}

Matrixoperator+(Matrix&& lhs, const Matrix& rhs){ lhs += rhs; return lhs; // copy lhs into return value }

Widget makeWidget(){ Widget w; … return std::move(w); // DO NOT apply move() on local variable. // It will hinder RVO. // If the conditions for the RVO are met, but // compiler choose not to perform copy elision, // the object being returned must be treated as // an rvalue. (std::move is implicitly applied.)}

Page 78: Effective Modern C++

Avoid overloading on universal references

template<typename T>void logAndAdd(T&& name) // (1){ auto now = std::chrono::system_clock::now(); log(now, “logAndAdd”); names.emplace(std::forward<T>(name)); }

void logAndAdd(int idx) // (2){ auto now = std::chrono::system_clock::now(); log(now, “logAndAdd”); names.emplace(nameFromIdx(idx));}

short nameIdx;logAndAdd(nameIdx); // Error! It will be resolved to logAndAdd(short&& name). Match (1). // Exactly match (1). // Promotion to match (2). Function (1) is better than (2).

• Functions taking universal references are the greediest functions in C++.They instantiate to create matches for almost any type of argument.

Page 79: Effective Modern C++

Avoid overloading on universal referencesclass Person {public: template<typename T> explicit Person(T&& n) // (1) : name(std::forward<T>(n)) { … }

explicit Person(int idx) // (2) : name(nameFromIdx(idx)) { … }

Person(const Person& rhs); // (3) copy ctor (compiler-generated) Person(Person&& rhs); // (4) move ctor (compiler-generated) …

private: std::string name;};

Person p(“Nancy”);auto cloneOfP(p); // Error! It will be resolved to Person(Person&). Match (1).

const Person cp(“Nancy”);auto cloneOfP(cp); // Calls copy ctor. Match (1) and (3). // Normal function is preferred.

Page 80: Effective Modern C++

Avoid overloading on universal referencesclass Person {public: template<typename T> explicit Person(T&& n) // (1) : name(std::forward<T>(n)) { … }

explicit Person(int idx) // (2) : name(nameFromIdx(idx)) { … }

Person(const Person& rhs); // (3) copy ctor (compiler-generated) Person(Person&& rhs); // (4) move ctor (compiler-generated) …

private: std::string name;};

class SpecialPerson : public Person {pubic: SpecialPerson(const SpecialPerson& rhs) // copy ctor : Person(rhs) { … } // Error. Calls (1), not base class copy ctor (3) SpecialPerson(SpecialPerson&& rhs) // move ctor : Person(std::move(rhs)) { … } // Error. Calls (1), not base class move ctor (4)};

Page 81: Effective Modern C++

Tag Dispatchtemplate<typename T>void logAndAdd(T&& name){ logAndAddImpl( std::forward<T>(name), std::is_integral<typename std::remove_reference<T>::type>() // remove reference before // pass to is_integral<T>() );}

template<typename T>void logAndAddImpl(T&& name, std::false_type){ auto now = std::chrono::system_clock::now(); log(now, “logAndAdd”); names.emplace(std::forward<T>(name));}

void logAndAddImpl(int idx, std::true_type){ logAndAdd(nameFromIdx(idx)); }

Page 82: Effective Modern C++

std::enable_if

class Person {public: // syntax: enable_if<condition>::type // is_base_of<T1, T2>::value is true if T2 is derived from T1 // std::decay<T>::type removes reference and cv-qualifiers(i.e., const or volatile) from T template< typename T, typename = typename std::enable_if< !std::is_base_of<Person, typename std::decay<T>::type >::value && !std::is_integral<typename std::remove_reference<T>::type >::value >::type > explicit Person(T&& n); // universal reference constructor … };

• std::enable_if gives you a way to force compilers to behave as if aparticular template didn’t exist.

Page 83: Effective Modern C++

Reference collapsing

When a lvalue is passed as an argument, T is deduced to be an lvalue reference.When an rvalue is passed, T is deduced to be a non-reference.

template<typename T>void func(T&& param);

Widget widgetFactory(); // function returning rvalue

Widget w; // a variable (an lvalue)

func(w); // T deduced to be Widget&. // func(Widget& && param) => func(Widget& param)

func(widgetFactory()); // T deduced to be Widget

• Reference to reference is illegal. • Compiler may produce reference to reference in particular contexts. • If either reference is an lvalue reference, the result is an lvalue reference.

Otherwise the result is an rvalue reference. • A universal reference isn’t a new kind of reference, it’s actually an rvalue

reference in a context where two conditions are satisfied: • Type deduction distinguishes lvalues from rvalues. Lvalues of type T

are deduced to have type T&, while rvalues of type T yield T as theirdeduced type.

• Reference collapsing occurs.

Page 84: Effective Modern C++

Perfect forwarding failure cases

template<typename T>void fwd(T&& param){ f(std::forward<T>(param)); }

// variadic formtemplate<typename… Ts>void fwd(Ts&&… params){ f(std::forward<Ts>(params)…);}

f(expression); // if this does one thing,fwd(expression); // but this does something else, fwd fails // to perfectly forward expression to f

• We want the forwarded-to function to be able to work with theoriginally-passed-in objects.

• Perfect forwarding fails when either of the following occurs: • Compilers are unable to deduce a type for one or more of

fwd’s parameters • Compilers deduce the “wrong” type for one or more of fwd’s

parameters

Page 85: Effective Modern C++

Perfect forwarding failure cases braced initializers

void f(const std::vector<int>& v); // declaration

f({1, 2, 3}); // fine, “{1, 2, 3}” implicitly converted to std::vector<int>

fwd({1, 2, 3}); // error! Compilers are forbidden from deducing a type for the // expression {1, 2, 3} in the call to fwd, because fwd’s parameter // isn’t declared to be a std::initializer_list.

// workaroundauto il = {1, 2, 3}; // il’s type deduced to be std::initializer_list<int>fwd(il);

Page 86: Effective Modern C++

Perfect forwarding failure cases Declaration-only integral static const data member

class Widget {public: static const std::size_t MinVals = 28; // Declaration-only, no memory allocated for it. …};

std::vector<int> widgetData;widgetData.reserve(Widget::MinVals); // Compilers will do const propagation.size_t* pt = &Widget::MinVals; // Link errors. Pointers need memory to address.

f(Widget::MinVals); // Fine, treated as f(28)fwd(Widget::MinVals); // Link errors. Reference is like pointer. It needs a location to address.

// workaroundconst std::size_t Widget::MinVals; // Give a definition in Widget’s .cpp file.

Page 87: Effective Modern C++

Perfect forwarding failure cases Overloaded function names and template names

int processVal(int value);int processVal(int value, int priority);

f(processVal); // Finefwd(processVal); // Error! There is no type information on processVal. No type deduction.

template<typename T>T workOnVal(T param){ …}

fwd(workOnVal); // Error! Which workOnVal instantiation? workOnVal represents many functions.

// workaroundusing ProcessFuncType = int (*)(int);ProcessFuncType processValPtr = processVal; // Use a variable to select which function you want.fwd(processValPtr); // Fine. processValPtr has type information. It could do type deduction.

fwd(static_cast<ProcessFuncType>(workOnVal)); // Fine. Select workOnVal<int> as parameter.

Page 88: Effective Modern C++

Perfect forwarding failure cases Bitfields

struct IPv4Header { std::uint32_t version:4, IHL:4, DSCP:6, ECN:2, totalLength:16, …};

void f(std::size_t sz);

IPv4Header h;

f(h.totalLength); // Fine.fwd(h.totalLength); // Error. There’s no way to bind a reference to arbitrary bits.

// workaroundauto length = static_cast<std::uint16_t>(h.totalLength); // Make a copy.fwd(length); // pass the copy to fwd