Effective Modern C++ 9.4分
读书笔记 第1页
Cynosure

Errata: http://www.aristeia.com/BookErrata/emc++-errata.html

Item2

int x1 = 27;

int x2(27);

int x3 = { 27 };

int x4{ 27 };

auto x1 = 27; // type is int, value is 27

auto x2(27); // ditto

auto x3 = { 27 }; // type is std::initializer_list<int>,value is { 27 }

auto x4{ 27 }; // ditto

Item 5: Prefer auto to explicit type declarations

auto variables have their type deduced from their initializer, so they must be initial‐ized.

auto x2; // error! initializer required

auto的优点:

- std::function approach is generally bigger and slower than the auto approach, and it mayyield out-of-memory exceptions, too.

-写法更简洁

- 避免“type shortcuts.”。例如unsigned sz = v.size();和for (const std::pair<std::string, int>& p : m){}

- auto types automatically change if the type of their initializing expressionchanges, and that means that some refactorings are facilitated by the use of auto。

auto的缺点:

- item 2,item6

- Some developers are disturbed by the fact that using auto eliminates the ability todetermine an object’s type by a quick glance at the source code.

。item6

“invisible” proxy classes don’t play well with auto. You therefore want to avoid code of this form:

auto someVar = expression of "invisible" proxy class type

Given double calcEpsilon();

float ep = calcEpsilon(); // this is not preferred

auto ep = static_cast<float>(calcEpsilon()); // this is preferred. (The explicitly typed initializer idiom)

.

.

Item 7: Distinguish between () and {} when creatingobjects.

():parentheses(简记为parens)

{}: braces

std::vector<int> v{ 1, 3, 5 }; // v's initial content is 1, 3, 5

class Widget {…

private:

int x{ 0 }; // fine, x's default value is 0

int y = 0; // also fine

int z(0); // error!

};

.

uncopyable objects (e.g., std::atomics—see Item 40) may beinitialized using braces or parentheses, but not using “=”:

std::atomic<int> ai1{ 0 }; // fine

std::atomic<int> ai2(0); // fine

std::atomic<int> ai3 = 0; // error!

.

braced initialization is that it prohibits implicit narrowing conver‐sions among built-in types:

double x, y, z;

int sum1{ x + y + z }; // error! sum of doubles may not be expressible as int

Widget w2(); // most vexing parse! declares a function named w2 that returns a Widget!

Widget w3{}; // calls Widget ctor with no args

The drawback to braced initialization,易混淆。

例子:

class Widget {

public:

Widget(int i, bool b); // ctors not declaring

Widget(int i, double d); // std::initializer_list params

。。。

};

Widget w1(10, true); // calls first ctor

Widget w2{10, true}; // also calls first ctor

Widget w3(10, 5.0); // calls second ctor

Widget w4{10, 5.0}; // also calls second ctor

例子:

class Widget {

public:

Widget(int i, bool b); // as before

Widget(int i, double d); // as before

Widget(std::initializer_list<long double> il); // added

};

Widget w1(10, true); // uses parens and, as before,calls first ctor

Widget w2{10, true}; // uses braces, but now calls std::initializer_list ctor (10 and true convert to long double)

Widget w3(10, 5.0); // uses parens and, as before, calls second ctor

Widget w4{10, 5.0}; // uses braces, but now calls std::initializer_list ctor (10 and 5.0 convert to long double)

例子:

class Widget {

public:Widget(int i, bool b); // as before

Widget(int i, double d); // as before

Widget(std::initializer_list<long double> il); // as before

operator float() const; // convert to float

};

Widget w5(w4); // uses parens, calls copy ctor

Widget w6{w4}; // uses braces, calls std::initializer_list ctor (w4 converts to float, and float converts to long double)

Widget w7(std::move(w4)); // uses parens, calls move ctor

Widget w8{std::move(w4)}; // uses braces, calls std::initializer_list ctor(for same reason as w6)

例子:

class Widget {

public:

Widget(int i, bool b); // as before

Widget(int i, double d); // as before

Widget(std::initializer_list<bool> il); // element type is now bool

…//no implicit conversion funcs

};

Widget w{10, 5.0}; // error! requires narrowing conversions

例子:

class Widget {public:

Widget(int i, bool b); // as before

Widget(int i, double d); // as before

Widget(std::initializer_list<std::string> il);//std::initializer_list element type is now std::string

… // no implicit conversion funcs

};

Widget w1(10, true); // uses parens, still calls first ctor

Widget w2{10, true}; // uses braces, now calls first ctor

Widget w3(10, 5.0); // uses parens, still calls second ctor

Widget w4{10, 5.0}; // uses braces, now calls second ctor

例子:

class Widget {public:

Widget(); // default ctor

Widget(std::initializer_list<int> il); // std::initializer_list ctor

… // no implicit conversion funcs

};

Widget w1; // calls default ctor

Widget w2{}; // also calls default ctor

Widget w3(); // most vexing parse! declares a function!

Widget w4({}); // calls std::initializer_list ctor with empty list

Widget w5{{}}; // constructor for w5 is called with a one-element std::initializer_list, not an empty one. For details, consult this blog post(http://scottmeyers.blogspot.com/2016/11/help-me-sort-out-meaning-of-as.html).

例子:

std::vector<int> v1(3, 4);// [4, 4, 4]

std::vector<int> v1{3, 4};// [3, 4]

There are two primary take‐aways from this discussion.

First, as a class author, you need to be aware that if yourset of overloaded constructors includes one or more functions taking a std::initializer_list, client code using braced initialization may see only the std::initializer_list overloads.

Second, as a class client, you must choose carefully between paren‐theses and braces when creating objects. Most developers...... On the other hand, .....There is no consensus that eitherapproach is better than the other, so my advice is to pick one and apply it consistently.

If you’re a template author, .....The author of doSomeWork can’t know. Onlythe caller can. This is precisely the problem faced by the Standard Library functionsstd::make_unique and std::make_shared (see Item 21). These functions resolvethe problem by internally using parentheses and by documenting this decision as partof their interfaces.

.

Item 8

In c++98:

void f(int); // three overloads of f

void f(bool);

void f(void*);

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

f(NULL); // might not compile, but typically calls f(int). Never calls f(void*)

.

nullptr’s advantage :

1.it doesn’t have an integral type, it doesn’t have a pointer type, nullptr’s actual type is std::nullptr_t. e.g.

f(nullptr); // calls f(void*) overload

2.improve code clarity,

2.1when autovariables are involved,

e.g.

auto result = findRecord( /* arguments */ );

if (result == nullptr) // rather than using (result == 0)

{…}

2.2when templates enter the picture

e.g.

int f1(std::shared_ptr<Widget> spw); // call these only when the appropriate mutex is locked

double f2(std::unique_ptr<Widget> upw); //

bool f3(Widget* pw); //

std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3

decltype(auto) lockAndCall(FuncType func, MuxType& mutex,PtrType ptr)

auto result1 = lockAndCall(f1, f1m, 0); // error

auto result2 = lockAndCall(f2, f2m, NULL); // error

auto result3 = lockAndCall(f3, f3m, nullptr); // fine

The fact that template type deduction deduces the “wrong” types for 0 and NULL is the most compelling reason to use nullptr instead of 0 or NULL when you want to refer to a null pointer. With nullptr, templates pose no special challenge. Combined with the fact that nullptr doesn’t suffer from the overload resolution surprises that 0 and NULL are susceptible to, the case is ironclad. When you want to refer to a null pointer, use nullptr, not 0 or NULL

.

Item 9: Prefer alias declarations to typedef s

alias declarations may be templatized(called alias templates), while typedefs cannot.

With an alias template:

template<typename T>

using MyAllocList = std::list<T, MyAlloc<T>>;

// client code

MyAllocList<Widget> lw;

template<typename T>

class Widget {

private:

MyAllocList<T> list;// non-dependent type

};

With a typedef:

template<typename T>

struct MyAllocList {

typedef std::list<T, MyAlloc<T>> type;

};

// client code

MyAllocList<Widget>::type lw;

template<typename T>

class Widget {

private:

typename MyAllocList<T>::type list;// Compiler can’t know for sure that it names a type

};

.

std::remove_const<T>::type // yields T from const T

std::remove_reference<T>::type // yields T from T& and T&&

std::add_lvalue_reference<T>::type // yields T& from T

.

alias templates are better than type synonym technology

std::remove_const<T>::type // C++11: const T → T

std::remove_const_t<T> // C++14 equivalent

std::remove_reference<T>::type// C++11: T&/T&& → T

std::remove_reference_t<T> // C++14 equivalent

std::add_lvalue_reference<T>::type// C++11: T → T&

std::add_lvalue_reference_t<T> // C++14 equivalent

.

Item 10: Prefer scoped enum s to unscoped enum s.

scoped enums: enum class

In C++11:

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

auto white = false; // fine, no other "white" in scope

Color c = white; // error! no enumerator named "white" is in this scope

Color c = Color::white; // fine

auto c = Color::white; // also fine (and in accord with Item 5's advice)

scoped enums advantages:

1. reduction in namespace pollution

2. scoped enums are much more strongly typed.

于此对比的是:unscoped enums implicitly convert to integral types (and, from there, to floating-point types)

enum class Color { black, white, red };

Color c = Color::red;

if (c < 14.5) { }// error! can't compare Color and double

if (static_cast<double>(c) < 14.5) {}// odd code, but it's valid

3. scoped enums may be forward-declared. the inability to forward-declare enums has drawbacks. The most notable is probably the increase in compilation dependencies.

the underlying type for a scoped enum is always known:

enum class Status; // forward declaration. // default underlying type is int

enum class Status: std::uint32_t; // underlying type for Status is std::uint32_t (from <cstdint>)

enum Color: std::uint8_t;// fwd decl for unscoped enum; underlying type is std::uint8_t

enum class Status: std::uint32_t { good = 0, failed = 1,...}

4.C++11’s std::tuples

using UserInfo = std::tuple<std::string, std::string, std::size_t> ;

UserInfo uInfo;

auto val = std::get<1>(uInfo); // get value of field 1

v.s.

enum UserInfoFields { uiName, uiEmail, uiReputation };

uto val = std::get<uiEmail>(uInfo); //ah, get value of email field

5. return the enum’s underlying type.

为了能写出这样的代码:

auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo);

需要实现函数toUType(...):

template<typename E>

constexpr typename std::underlying_type<E>::type

toUType(E enumerator) noexcept{

return static_cast<typename std::underlying_type<E>::type>(enumerator);

}

//在C++14可简化为:

template<typename E>

constexpr std::underlying_type_t<E>

toUType(E enumerator) noexcept{

return static_cast<std::underlying_type_t<E>>(enumerator);

}

或更简化为:

template<typename E>

constexpr auto toUType(E enumerator) noexcept{

return static_cast<std::underlying_type_t<E>>(enumerator);

}

.

Item 11: Prefer deleted functions to private undefined ones.

//C++98

template <class charT, class traits = char_traits<charT> >

class basic_ios : public ios_base {

public:…

private:

basic_ios(const basic_ios& ); // not defined

basic_ios& operator=(const basic_ios&); // not defined

};

//c++11

template <class charT, class traits = char_traits<charT> >

class basic_ios : public ios_base {

public:

basic_ios(const basic_ios& ) = delete;

basic_ios& operator=(const basic_ios&) = delete;

};

Why deleted functions are declared public? Because When client code tries to use a member function, C++ checks accessibility before deleted status. When client code tries to use a deleted private function, some compilers complain only about the function being private, even though the function’s accessibility doesn’t really affect whether it can be used.

the advantages of deleted functions:

1. code that’s in member and friend functions will fail to compile if it tries to copy basic_ios objects. That’s an improvement over the C++98 behavior, where such improper usage wouldn’t be diagnosed until link-time.

2.(any function may be deleted, while only member functions may be private.)

bool isLucky(int number); // original function

bool isLucky(char) = delete; // reject chars

bool isLucky(bool) = delete; // reject bools

bool isLucky(double) = delete; // reject doubles and floats

3.prevent use of template instantiations that should be disabled. (private member functions can’t). if you have a function template inside a class, and you’d like to disable some instantiations by declaring them private (à la classic C++98 convention), you can’t, because it’s not possible to give a member function template specialization a different access level from that of the main template.

class Widget {public:

template<typename T> void processPointer(T* ptr) { … }

private:

template<> void processPointer<void>(void*); // error!

};

template<> void Widget::processPointer<void>(void*) = delete; // still public, but deleted

..

void* pointers: there is no way to dereference them, to increment or decrement them, etc

char* pointers: they typically represent pointers to C-style strings, not pointers to individual characters

.

Item 12: Declare overriding functions override.

class Widget {

public:

void doWork() &; // this version of doWork applies only when *this is an lvalue

void doWork() &&; // this version of doWork applies only when *this is an rvalue

};

Widget makeWidget(); // factory function (returns rvalue)

Widget w; // normal object (an lvalue)

w.doWork(); // calls Widget::doWork for lvalues (i.e., Widget::doWork &)

makeWidget().doWork(); // calls Widget::doWork for rvalues (i.e., Widget::doWork &&)

.

class Widget {

public:

using DataType = std::vector<double>;

DataType& data() &{ return values; } // for lvalue Widgets, return lvalue

DataType data() && { return std::move(values); } // for rvalue Widgets, return rvalue

private:

DataType values;

};

auto vals1 = w.data(); // calls lvalue overload for Widget::data, copy-constructs vals1

auto vals2 = makeWidget().data(); // calls rvalue overload for Widget::data, move-constructs vals2

.

Item 13: Prefer const_iterators to iterators.

const_iterators simply don’t convert to iterators. That’s not a C++98 restriction. It’s true in C++11, too. const.

std::vector<int> values;

std::vector<int>::iterator it = std::find(values.begin(),values.end(), 1983); // in C++98

values.insert(it, 1998);

auto it = std::find(values.cbegin(),values.cend(), 1983); // In C++11, use cbegin and cend

values.insert(it, 1998);

.

// C++14

template<typename C, typename V>

void findAndInsert(C& container, const V& targetVal, const V& insertVal) // in container, find first occurrence of targetVal, then insert insertVal there

{

using std::cbegin; using std::cend;

auto it = std::find(cbegin(container), cend(container), targetVal);// non-member cbegin non-member cend

container.insert(it, insertVal);

}

.

// C++11, write your own non-member cbegin(...)

template <class C>

auto cbegin(const C& container)->decltype(std::begin(container))

{

return std::begin(container); // Invoking the nonmember begin function (provided by C++11) on a const container yields a const_iterator

}

.

Item 14: Declare functions noexcept if they won’t emit exceptions

In C++11, unconditional noexcept is for functions that guarantee they won’t emit exceptions

Advantages of adding noexcept to a function which won’t emit an exception:

- it is a good interface specification.

- it permits compilers to generate better object code:

1.

int f(int x) throw(); // no exceptions from f: C++98 style. the call stack is unwound to f’s caller, and, after some actions not relevant here, program execution is terminated. less optimizable.

int f(int x) noexcept; // no exceptions from f: C++11 style. the stack is only *possibly* unwound before program execution is terminated. most optimizable.

int f(int x) // less optimizable

2.

In C++11, a natural optimization would be to replace the copying of std::vector elements with moves. 但如果move了n个元素后,在move第n+1个元素时抛出异常。此时无法回退至move前的状态(因为n个元素已经被move了)。这种情况无解。因此,C++11 implementations can’t silently replace copy operations inside push_back with moves unless it’s known that the move operations won’t emit exceptions。In that case, having moves replace copies would be safe(不会产生上述问题), and the only side effect would be improved performance(因为被move替代).

std::vector::push_back, std::vector::reserve, std::deque::insert, etc. All these functions replace calls to copy operations in C++98 with calls to move operations in C++11 only if the move operations are known to not emit exceptions.

3.

template <class T, size_t N>

void swap(T (&a)[N], T (&b)[N]) noexcept(noexcept(swap(*a, *b)));

Exception-neutral functions are never noexcept

Twisting a function’s implementation to permit a noexcept declaration 是不可取的

By default, all memory deallocation functions and all destructors—both user-defined and compilergenerated—are implicitly noexcept. There’s thus no need to declare them noexcept.(Doing so doesn’t hurt anything, it’s just unconventional.)

functions with wide contracts:Such a function may be called regardless of the state of the program, and it imposes no constraints on the arguments that callers pass it. Functions with wide contracts never exhibit undefined behavior. declaring wide contracts functions as noexcept

functions with narrow contracts:Functions without wide contracts have narrow contracts. For such functions, if a precondition is violated, results are undefined.

.

Item 15: Use constexpr whenever possible

translation = compilation + linking.

all constexpr objects are const, but not all const objects are constexpr.

constexpr functions: it says that if input parameters are compile-time constants, pow’s result may be used as a compile-time constant. If input parameters are not compile-time constants, the result will be computed at runtime.

some computations traditionally done at runtime can migrate to compile time. The more code taking part in the migration, the faster your software will run. (Compilation may take longer, however.)

in C++11, constexpr member functions are implicitly const.

.

Item 16: Make const member functions thread safe.

const+mutable has a problem in thread context. This problem can be resovled by employing mutex. But the whole class object will become move-only object because mutex is move-only.

.

Item 17: Understand special member function generation.

class Widget {

public:

Widget(Widget&& rhs); // move constructor

Widget& operator=(Widget&& rhs); // move assignment operator

};

the move constructor move-constructs each nonstatic data member of the class from the corresponding member of its parameter rhs, and the move assignment operator move-assigns each non-static data member from its parameter. The move constructor also move-constructs its base class parts (if there are any), and the move assignment operator move-assigns its base class parts.

a memberwise move consists of move operations on data members and base classes that support move operations.

move constructor <--> move assignment operator, 声明其中一个,就会让编译器不产生另一个.

copy operation (construction or assignment)<-->move operations (construction or assignment), 声明其中一个,就会让编译器不产生另一个

The Rule of Three states that if you declare any of a copy constructor, copy assignment operator, or destructor, you should declare all three.

So move operations are generated for classes (when needed) 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.

in C++11, declaring copy operations or a destructor --> not automatically generate copy operations.

So if your code depends on the generation of copy operations. you should use '=default'.

class Base

{

public:

virtual ~Base() = default; // make dtor virtual。若自定义destructor,则不会生成move operations。

Base(Base&&) = default; // support moving。所以如果想支持move operations,就需要添加“= default”

Base& operator=(Base&&) = default;

Base(const Base&) = default; // support copying。Declaring the move operations disables the copy operations, 所以如果想支持copyability,就需要添加“= default”

Base& operator=(const Base&) = default;

};

如果为class添加了destructor,则不会生成move operations。则client code里的move操作会变成copy操作,这有损性能。解决方法是为the copy and move operations添加“= default”

Destructor:

is noexcept by default。

Copy constructor:

Deleted if the class declares a move operation. Generation of this function in a class with a user-declared copy assignment operator or destructor is deprecated.

Copy assignment operator:

Generated only if the class lacks a user-declared copy assignment operator.

Deleted if the class declares a move operation.

Generation of this function in a class with a user-declared copy constructor or destructor is deprecated.

Move constructor and move assignment operator:

Generated only if the class contains no userdeclared copy operations, move operations, or destructor

对于member function template:

class Widget {

template<typename T> Widget(const T& rhs);// 如果T为Widget,会怎样?见Item26

template<typename T> Widget& operator=(const T& rhs);// 如果T为Widget,会怎样?见Item26

};

Item 18: Use std::unique_ptr for exclusive-ownership resource management.

auto delInvmt = [](Investment* pInvestment) // custom deleter(a lambdaexpression)

{

makeLogEntry(pInvestment);

delete pInvestment;

};

//---

template<typename... Ts>

std::unique_ptr<Investment, decltype(delInvmt)> makeInvestment(Ts&&... params)// revised return type

{

std::unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr, delInvmt); // ptr to be returned

if ( /* a Stock object should be created */ ){pInv.reset(new Stock(std::forward<Ts>(params)...));}

else if ( /* a Bond object should be created */ ){pInv.reset(new Bond(std::forward<Ts>(params)...));}

else if ( /* a RealEstate object should be created */ ){pInv.reset(new RealEstate(std::forward<Ts>(params)...));}

return pInv;

}

delInvmt is the custom deleter

auto delInvmt1 = [](Investment* pInvestment){...}

template<typename... Ts>

std::unique_ptr<Investment, decltype(delInvmt1)> // return type has size of Investment*

makeInvestment(Ts&&... args);

void delInvmt2(Investment* pInvestment) // custom deleter as function

{

makeLogEntry(pInvestment);

delete pInvestment;

}

template<typename... Ts>

std::unique_ptr<Investment, void (*)(Investment*)> // return type has size of Investment* + at least size of function pointer! 体积更大

makeInvestment(Ts&&... params);

.

.

std::unique_ptr<T[]> //when you’re using a C-like API that returns a raw pointer to a heap array

std::shared_ptr<Investment> sp = makeInvestment( arguments );// converts std::unique_ptr to std::shared_ptr

.

.

Item 19: Use std::shared_ptr for shared-ownership resource management.

move construction is faster than copy construction,

move assignment is faster than copy assignment.

supports custom deleters, but it differs from that for std::unique_ptr.

auto loggingDel = [](Widget *pw){ makeLogEntry(pw); delete pw; }; // custom deleter(as in Item 18)

std::unique_ptr<Widget, decltype(loggingDel)> upw(new Widget, loggingDel); // deleter type is part of ptr type

std::shared_ptr<Widget> spw(new Widget, loggingDel);// deleter type is not part of ptr type, this is more flexible

pw1 = pw2; // fine, std::unique_ptrs cannot do this.

void fool(std::shared_ptr<Widget> p); fool(pw1); // fine. std::unique_ptrs cannot do this.

constructing more than one std::shared_ptr from a single raw pointer is BAD! like this:

auto pw = new Widget;

std::shared_ptr<Widget> spw1(pw, loggingDel);

std::shared_ptr<Widget> spw2(pw, loggingDel); // this is bad

std::shared_ptrs的性能开销:p133

std::shared_ptrs can’t do is work with arrays。 std::shared_ptr<T[]>的缺点:p133

Item 20: Use std::weak_ptr for std::shared_ptrlike pointers that can dangle.

std::weak_ptrs can’t be dereferenced, nor can they be tested for nullness.

That’s because std::weak_ptr isn’t a standalone smart pointer. It’s an augmentation of std::shared_ptr.

std::weak_ptrs are typically created from std::shared_ptrs.

auto spw = std::make_shared<Widget>();

std::weak_ptr<Widget> wpw(spw);

spw = nullptr; // RC goes to 0, and the Widget is destroyed. wpw now dangles

std::weak_ptr --> std::shared_ptr的两种方法:

1. std::shared_ptr<Widget> spw1 = wpw.lock(); // The std::shared_ptr is null if the wpw has expired:

2. std::shared_ptr<Widget> spw3(wpw); // it throws std::bad_weak_ptr if wpw has expired,

std::weak_ptr避免std::shared_ptr之间的循环

std::weak_ptrs don’t participate in the shared ownership of objects and hence don’t affect the pointed-to object’s reference count.

Item 21: Prefer std::make_unique and std::make_shared to direct use of new.

======================================================

Things to Remember

Item 8

• Prefer nullptr to 0 and NULL.

• Avoid overloading on integral and pointer types.

Item 9: Prefer alias declarations to typedef s

• typedefs don’t support templatization, but alias declarations do.

• Alias templates avoid the “::type” suffix and, in templates, the “typename” prefix often required to refer to typedefs.

• C++14 offers alias templates for all the C++11 type traits transformations.

Item 10: Prefer scoped enum s to unscoped enum s.

• C++98-style enums are now known as unscoped enums.

• Enumerators of scoped enums are visible only within the enum. They convert to other types only with a cast.

• Both scoped and unscoped enums support specification of the underlying type. The default underlying type for scoped enums is int. Unscoped enums have no default underlying type.

• Scoped enums may always be forward-declared. Unscoped enums may be forward-declared only if their declaration specifies an underlying type.

Item 11: Prefer deleted functions to private undefined ones.

• Prefer deleted functions to private undefined ones.

• Any function may be deleted, including non-member functions and template instantiations.

Item 12: Declare overriding functions override.

• Declare overriding functions override.

• Member function reference qualifiers make it possible to treat lvalue and

rvalue objects (*this) differently

Item 13: Prefer const_iterators to iterators.

• Prefer const_iterators to iterators.

• In maximally generic code, prefer non-member versions of begin, end,

rbegin, etc., over their member function counterparts.

.

Item 14:

• noexcept is part of a function’s interface, and that means that callers may depend on it.

• noexcept functions are more optimizable than non-noexcept functions.

• noexcept is particularly valuable for the move operations, swap, memory deallocation functions, and destructors.

• Most functions are exception-neutral rather than noexcept.

.

Item 15 constexpr

• constexpr objects are const and are initialized with values known during compilation.

• constexpr functions can produce compile-time results when called with arguments whose values are known during compilation.

• constexpr objects and functions may be used in a wider range of contexts than non-constexpr objects and functions.

• constexpr is part of an object’s or function’s interface.

.

Item 16

• Make const member functions thread safe unless you’re certain they’ll never be used in a concurrent context.

• Use of std::atomic variables may offer better performance than a mutex, but they’re suited for manipulation of only a single variable or memory location.

Item 17: Understand special member function generation.

• The special member functions are those compilers may generate on their own: default constructor, destructor, copy operations, and move operations.

• Move operations are generated only for classes lacking explicitly declared move operations, copy operations, and a destructor.

• The copy constructor is generated only for classes lacking an explicitly declared copy constructor。it’s deleted if a move operation is declared.

The copy assignment operator is generated only for classes lacking an explicitly declared copy assignment operator, and it’s deleted if a move operation is declared.

Generation of the copy operations in classes with an explicitly declared destructor is deprecated.

• Member function templates never suppress generation of special member functions.

Item 18: Use std::unique_ptr for exclusive-ownership resource management.

• std::unique_ptr is a small, fast, move-only smart pointer for managing resources with exclusive-ownership semantics.

• By default, resource destruction takes place via delete, but custom deleters can be specified. Stateful deleters and function pointers as deleters increase the size of std::unique_ptr objects.

• Converting a std::unique_ptr to a std::shared_ptr is easy.

.

Item 19: Use std::shared_ptr for shared-ownership resource management.

• std::shared_ptrs offer convenience approaching that of garbage collection for the shared lifetime management of arbitrary resources.

• Compared to std::unique_ptr, std::shared_ptr objects are typically twice as big, incur overhead for control blocks, and require atomic reference count manipulations.

• Default resource destruction is via delete, but custom deleters are supported. The type of the deleter has no effect on the type of the std::shared_ptr. • Avoid creating std::shared_ptrs from variables of raw pointer type.

Item 20: Use std::weak_ptr for std::shared_ptrlike pointers that can dangle.

• Use std::weak_ptr for std::shared_ptr-like pointers that can dangle.

• Potential use cases for std::weak_ptr include caching, observer lists, and the prevention of std::shared_ptr cycles.

Item 21: Prefer std::make_unique and std::make_shared to direct use of new.

0
《Effective Modern C++》的全部笔记 5篇
豆瓣
免费下载 iOS / Android 版客户端