When I refer to a function object , I usually mean an object of a type supporting an operator()
member function. In other words, an object that acts like a function. Occasionally I use the term in a slightly more general sense to mean anything that can be invoked using the syntax of a non-member function call (i.e., “ function Name(arguments)
”). This broader definition covers not just objects supporting operator(), but also functions and C-like function pointers. (The narrower definition comes from C++98, the broader one from C++11.) Generalizing further by adding member function pointers yields what are known as callable objects . You can generally ignore the fine distinctions and simply think of function objects and callable objects as things in C++ that can be invoked using some kind of function-calling syntax.
Function objects created through lambda expressions are known as closures . It's seldom necessary to distinguish between lambda expressions and the closures they create, so I often refer to both as lambdas . Similarly, I rarely distinguish between function templates (i.e., templates that generate functions) and template functions (i.e., the functions generated from function templates). Ditto for class templates and template classes .
Many things in C++ can be both declared and defined. Declarations introduce names and types without giving details, such as where storage is located or how things are implemented:
extern int x; // object declaration
class Widget; // class declaration
bool func(const Widget& w); // function declaration
enum class Color; // scoped enum declaration
// (see Item 10)
Definitions provide the storage locations or implementation details:
int x; // object definition
class Widget { // class definition
…
};
bool func(const Widget& w)
{ return w.size() < 10; } // function definition
enum class Color
{ Yellow, Red, Blue }; // scoped enum definition
A definition also qualifies as a declaration, so unless it's really important that something is a definition, I tend to refer to declarations.
I define a function's signature to be the part of its declaration that specifies parameter and return types. Function and parameter names are not part of the signature. In the example above, func
's signature is bool(const Widget&)
. Elements of a function's declaration other than its parameter and return types (e.g., noexcept
or constexpr
, if present), are excluded. ( noexcept
and constexpr
are described in Items 14and 15.) The official definition of “signature” is slightly different from mine, but for this book, my definition is more useful. (The official definition sometimes omits return types.)
New C++ Standards generally preserve the validity of code written under older ones, but occasionally the Standardization Committee deprecates features. Such features are on standardization death row and may be removed from future Standards. Compilers may or may not warn about the use of deprecated features, but you should do your best to avoid them. Not only can they lead to future porting headaches, they're generally inferior to the features that replace them. For example, std::auto_ptr
is deprecated in C++11, because std::unique_ptr
does the same job, only better.
Sometimes a Standard says that the result of an operation is undefined behavior . That means that runtime behavior is unpredictable, and it should go without saying that you want to steer clear of such uncertainty. Examples of actions with undefined behavior include using square brackets (“ []
”) to index beyond the bounds of a std::vector
, dereferencing an uninitialized iterator, or engaging in a data race (i.e., having two or more threads, at least one of which is a writer, simultaneously access the same memory location).
I call built-in pointers, such as those returned from new
, raw pointers . The opposite of a raw pointer is a smart pointer . Smart pointers normally overload the pointer-dereferencing operators ( operator->
and operator*
), though Item 20explains that std::weak_ptr
is an exception.
In source code comments, I sometimes abbreviate “constructor” as ctor
and “destructor” as dtor
.
Reporting Bugs and Suggesting Improvements
I've done my best to fill this book with clear, accurate, useful information, but surely there are ways to make it better. If you find errors of any kind (technical, expository, grammatical, typographical, etc.), or if you have suggestions for how the book could be improved, please email me at emc++@aristeia.com. New printings give me the opportunity to revise Effective Modern C++, and I can't address issues I don't know about!
To view the list of the issues I do know about, consult the book's errata page, http:// www.aristeia.com/BookErrata/emc++-errata.html.
C++98 had a single set of rules for type deduction: the one for function templates. C++11 modifies that ruleset a bit and adds two more, one for auto
and one for decltype
. C++14 then extends the usage contexts in which auto
and decltype
may be employed. The increasingly widespread application of type deduction frees you from the tyranny of spelling out types that are obvious or redundant. It makes C++ software more adaptable, because changing a type at one point in the source code automatically propagates through type deduction to other locations. However, it can render code more difficult to reason about, because the types deduced by compilers may not be as apparent as you'd like.
Without a solid understanding of how type deduction operates, effective programming in modern C++ is all but impossible. There are just too many contexts where type deduction takes place: in calls to function templates, in most situations where auto
appears, in decltype
expressions, and, as of C++14, where the enigmatic decltype(auto)
construct is employed.
This chapter provides the information about type deduction that every C++ developer requires. It explains how template type deduction works, how auto
builds on that, and how decltype
goes its own way. It even explains how you can force compilers to make the results of their type deductions visible, thus enabling you to ensure that compilers are deducing the types you want them to.
Item 1: Understand template type deduction.
When users of a complex system are ignorant of how it works, yet happy with what it does, that says a lot about the design of the system. By this measure, template type deduction in C++ is a tremendous success. Millions of programmers have passed arguments to template functions with completely satisfactory results, even though many of those programmers would be hard-pressed to give more than the haziest description of how the types used by those functions were deduced.
If that group includes you, I have good news and bad news. The good news is that type deduction for templates is the basis for one of modern C++'s most compelling features: auto. If you were happy with how C++98 deduced types for templates, you're set up to be happy with how C++11 deduces types for auto
. The bad news is that when the template type deduction rules are applied in the context of auto
, they sometimes seem less intuitive than when they're applied to templates. For that reason, it's important to truly understand the aspects of template type deduction that auto
builds on. This Item covers what you need to know.
Читать дальше