logo资料库

Effective Modern C++完整版.pdf

第1页 / 共319页
第2页 / 共319页
第3页 / 共319页
第4页 / 共319页
第5页 / 共319页
第6页 / 共319页
第7页 / 共319页
第8页 / 共319页
资料共319页,剩余部分请下载后查看
Cover
Contents
Introduction
Terminology and Conventions
Reporting Bugs and Suggesting Improvements
Chapter 1 Deducing Types
Item 1: Understand template type deduction.
Case 1: ParamType is a Pointer or Reference, but not a Universal Reference
Case 2: ParamType is a Universal Reference
Case 3: ParamType is Neither a Pointer nor a Reference
Array Arguments
Function Arguments
Item 2: Understand auto type deduction.
Item 3: Understand decltype.
Item 4: Know how to view deduced types.
IDE Editors
Compiler Diagnostics
Runtime Output
Beyond typeid
Chapter 2 auto
Item 5: Prefer auto to explicit type declarations.
Item 6: Be aware of the typed initializer idiom.
Chapter 3 From C++98 to C++11 and C++14
Item 7: Distinguish () and {} when creating objects.
Item 8: Prefer nullptr to 0 and NULL.
Item 9: Prefer alias declarations to typedefs.
Item 10: Prefer scoped enums to unscoped enums.
Item 11: Prefer deleted functions to private undefined ones.
Item 12: Declare overriding functions override.
Item 13: Prefer const_iterators to iterators.
Item 14: Use constexpr whenever possible.
Item 15: Make const member functions thread-safe.
Item 16: Declare functions noexcept whenever possible.
Item 17: Consider pass by value for cheap-to-move parameters that are always copied .
Item 18: Consider emplacement instead of insertion.
Item 19: Understand special member function generation.
Chapter 4 Smart Pointers
Item 20: Use std::unique_ptr for exclusive-ownership resource management.
Item 21: Use std::shared_ptr for shared-ownership resource management.
Item 22: Use std::weak_ptr for std::shared_ptr-like pointers that can dangle.
Item 23: Prefer std::make_unique and std::make_shared to direct use of new.
Item 24: When using the Pimpl Idiom, define special member functions in the implementation file.
Chapter 5 Rvalue References, Move Semantics, and Perfect Forwarding
Item 25: Understand std::move and std::forward.
Item 26: Distinguish universal references from rvalue references.
Item 27: Use std::move on rvalue references, std::forward on universal references.
Item 28: Avoid overloading on universal references.
Item 29: Familiarize yourself with alternatives to overloading on universal references.
Abandoning overloading
Passing by const T&
Passing by value
Tag dispatch
Tag dispatch constructors
Trade-offs
Item 30: Understand reference collapsing.
Item 31: Assume that move operations are not present, not cheap, and not used.
Item 32: Familiarize yourself with perfect forwarding failure cases.
Braced initializers
0 or NULL as null pointers
Declaration-only integral static const data members
Overloaded function names and template names
Bitfields
Upshot
Chapter 6 Lambda Expressions
Item 33: Avoid default capture modes.
Item 34: Use init capture to move objects into closures.
Item 35: Use decltype on auto&& parameters to std::forward them.
Item 36: Prefer lambdas to std::bind.
Chapter 7 The Concurrency API
Item 37: Prefer task-based programming to thread-based.
Item 38: Specify std::launch::async if asynchronicity is essential.
Item 39: Make std::threads unjoinable on all paths.
Item 40: Be aware of varying thread handle destructor behavior.
Item 41: Consider void futures for one-shot event communication.
Item 42: Use std::atomic for concurrency, volatile for special memory.
1 2 3 4 5 6 7 8 9 Introduction Chapter 1 Deducing Types Item 1: Understand template type deduction. Item 2: Understand auto type deduction. Item 3: Understand decltype. Item 4: Know how to view deduced types. Chapter 2 auto Item 5: Prefer auto to explicit type declarations. 10 Item 6: Be aware of the typed initializer idiom. 11 Chapter 3 From C++98 to C++11 and C++14 12 13 14 15 16 17 18 19 20 21 Item 7: Distinguish () and {} when creating objects. Item 8: Prefer nullptr to 0 and NULL. Item 9: Prefer alias declarations to typedefs. Item 10: Prefer scoped enums to unscoped enums. Item 11: Prefer deleted functions to private undefined ones. Item 12: Declare overriding functions override. Item 13: Prefer const_iterators to iterators. Item 14: Use constexpr whenever possible. Item 15: Make const member functions thread-safe. Item 16: Declare functions noexcept whenever possible. Page 1 4 11 11 21 26 34 42 42 48 56 57 67 72 77 84 89 96 101 107 113
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Item 17: Consider pass by value for cheap-to-move parameters that are always copied. Item 18: Consider emplacement instead of insertion. Item 19: Understand special member function generation. Chapter 4 Smart Pointers Item 20: Use std::unique_ptr for exclusive-ownership resource management. Item 21: Use std::shared_ptr for shared-ownership resource management. 120 128 136 145 147 154 Item 22: Use std::weak_ptr for std::shared_ptr-like pointers that can dangle. 164 Item 23: Prefer std::make_unique and std::make_shared to direct use of new. 170 Item 24: When using the Pimpl Idiom, define special member functions in the implementation file. 179 16 Chapter 5 Rvalue References, Move Semantics, and Perfect Forwarding 189 17 18 19 20 21 22 23 24 Item 25: Understand std::move and std::forward. Item 26: Distinguish universal references from rvalue references. 190 197 Item 27: Use std::move on rvalue references, std::forward on universal references. Item 28: Avoid overloading on universal references. 202 210 Item 29: Familiarize yourself with alternatives to overloading on universal references. Item 30: Understand reference collapsing. 217 228 Page 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Item 31: Assume that move operations are not present, not cheap, and not used. 234 Item 32: Familiarize yourself with perfect forwarding failure cases. 238 Chapter 6 Lambda Expressions Item 33: Avoid default capture modes. Item 34: Use init capture to move objects into closures. 249 251 258 Item 35: Use decltype on auto&& parameters to std::forward them. 264 Item 36: Prefer lambdas to std::bind. Chapter 7 The Concurrency API Item 37: Prefer task-based programming to thread-based. 267 275 275 Item 38: Specify std::launch::async if asynchronicity is essential. 280 Item 39: Make std::threads unjoinable on all paths. Item 40: Be aware of varying thread handle destructor behavior. 286 293 Item 41: Consider void futures for one-shot event communication. 299 Item 42: Use std::atomic for concurrency, volatile for special memory. 308 17 Page 3
1 2 3 4 5 6 7 8 9 If you’re an experienced C++ programmer and are anything like me, you initially approached C++11 thinking, “Yes, yes, I get it. It’s C++, only more so.” But as your knowledge of the revised language increased, you were surprised by the scale of the changes. auto objects, range-based for loops, lambda expressions, and rvalue references change the very face of C++, to say nothing of the new concurrency fea- tures. And then there are the idiomatic changes! 0 and typedefs are out, nullptr and alias declarations are in. Enums should now be scoped. Smart pointers are now preferable to built-in ones. Moving objects is normally better than copying 10 them. There’s a lot to learn about C++11. 11 The adoption of C++14 hardly made things easier. 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 So...a lot to learn. More importantly, a lot to learn about making effective use of the new capabilities. If you need information on the basic syntax or semantics of fea- tures in “modern” C++, resources abound, but if you’re looking for guidance on how to employ the features to create software that’s correct, efficient, maintaina- ble, and portable, the search is more challenging. That’s where this book comes in. It’s devoted not to describing the features of C++11 and C++14, but rather to their effective application. The information in the book is broken into guidelines called Items. Want to under- stand the various forms of type deduction? Or to know when (and when not) to declare objects using auto? Are you interested in why const member functions should be thread-safe, how to implement the Pimpl Idiom using std::unique_ptr, why you should avoid default capture modes in lambda ex- pressions, or the differences between (and proper uses of) std::atomic and volatile? The answers are all here. Furthermore, they’re platform-independent, Standards-conformant answers. This is a book about portable C++. Items comprise guidelines, not rules, because guidelines have exceptions. The most important part of each Item is not the advice it offers, but the rationale be- hind the advice. Once you’ve read that, you’ll be in a position to determine whether the circumstances of your project justify disregarding the Item’s guidance. The Page 4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 true goal of this book isn’t to tell you what to do or what to avoid doing, but to convey a deeper understanding of how things work in C++11 and C++14. Terminology and Conventions To make sure we understand one another, it’s important to agree on some termi- nology, beginning, ironically, with “C++.” There have been four standardized ver- sions of C++, each named after the year in which the corresponding ISO Standard was adopted: C++98, C++03, C++11, and C++14. C++98 and C++03 differ only in subtle technical details, so in this book, I refer to both as C++98. When I mention C++98, I mean only that version of the language. Where I refer to C++11, I mean both C++11 and C++14, because C++14 is effectively a superset of C++11. When I write C++14, I mean specifically C++14. And if I simply mention C++, I’m making a broad statement that pertains to all language versions. As a re- sult, I might say that C++ places a premium on efficiency (true for all versions), that C++98 lacks support for concurrency (true for C++98 only), that C++11 sup- ports lambda expressions (true for C++11 and C++14), and that C++14 offers gen- eralized function return type deduction (true for C++14 only). C++11’s most pervasive feature is probably move semantics, and the foundation of move semantics is distinguishing expressions that are rvalues from those that are lvalues. That’s because rvalues indicate objects eligible for move operations, while lvalues generally don’t. In concept (though not always in practice), rvalues corre- spond to anonymous temporary objects returned from functions, while lvalues correspond to objects you can refer to, either by name or by following a pointer or reference. A useful heuristic to determine whether an expression is an lvalue is to ask if you can take its address. If you can, it typically is. If you can’t, it’s usually an rvalue. A nice feature of this heuristic is that it helps you remember that the type of an ex- pression is independent of whether the expression is an lvalue or an rvalue. That is, given a type T, you can have both lvalues of type T and rvalues of type T. It’s es- pecially important to remember this when dealing with a parameter of rvalue ref- erence type, because the parameter itself is an lvalue: Page 5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Widget { public: Widget(Widget&& rhs); // rhs is an lvalue, though it has … // an rvalue reference type }; Here, it’d be perfectly valid to take rhs’s address inside Widget’s move construc- tor, so rhs is an lvalue, even though its type is an rvalue reference. (By similar rea- soning, all parameters are lvalues.) This code example demonstrates several conventions I typically follow:  The class name is Widget. I use Widget whenever I want to refer to an arbi- trary user-defined type. Unless I need to show specific details of the class, I use Widget without declaring it.  I use the parameter name rhs, which stands for “right-hand side.” It’s my pre- ferred parameter name for the move operations (i.e., move constructor and move assignment operator) and the copy operations (i.e., copy constructor and copy assignment operator), though I also employ it for the right-hand parame- ter of binary operators: Matrix operator+(const Matrix& lhs, const Matrix& rhs); It’s no surprise, I hope, that lhs stands for “left-hand side.”  I highlight parts of code or parts of comments to draw your attention to them. In the code above, I’ve highlighted the declaration of rhs and the part of the comment noting that rhs is an lvalue.  I use “…” to indicate “other code could go here.” This narrow ellipses is differ- ent from the wide ellipsis (“...”) that’s used in the source code for C++11’s variadic templates. That sounds confusing, but it’s not. For example: template // these are C++ void processVals(const Ts&... params) // source code { // ellipses … // this means "some // code goes here" } Page 6
1 2 3 4 5 6 7 8 9 The declaration of processVals shows that I use typename when declaring type parameters in templates, but that’s merely a personal preference; the keyword class would work just as well. On those few occasions where I show code excerpts from a C++ Standard, I declare type parameters using class, be- cause that’s what the Standards do. When an object is initialized with another object of the same type, the new object is said to be a copy of the initializing object, even if the copy was created via the move constructor. Regrettably, there’s no terminology in C++ that distinguishes between an object that’s a copy-constructed copy and one that’s a move- 10 constructed copy: 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 void someFunc(Widget w); // someFunc's parameter w // is passed by value Widget wid; // wid is some Widget someFunc(wid); // in this call to someFunc, // w is a copy of wid that's // created via copy construction someFunc(std::move(wid)); // in this call to SomeFunc, // w is a copy of wid that's // created via move construction Copies of rvalues are generally move-constructed, while copies of lvalues are typi- cally copy-constructed. An implication is that if you know only that an object is a copy of another object, it's not possible to say how expensive it was to construct the copy. In the code above, for example, there’s no way to say how expensive it is to create the parameter w without knowing whether rvalues or lvalues are passed to someFunc. (You’d also have to know the cost of moving and copying Widgets.) In a function call, the expressions passed at the call site are the function’s argu- ments. The arguments are used to initialize the function’s parameters. In the first call to someFunc above, the argument is wid. In the second call, the argument is std::move(wid). In both calls, the parameter is w. The distinction between argu- ments and parameters is important, because parameters are lvalues, but the ar- guments with which they are initialized may be rvalues or lvalues. This is especial- ly relevant during the process of perfect forwarding, whereby an argument passed Page 7
分享到:
收藏