template
auto cbegin(const C& container)->decltype(std::begin(container)) {
return std::begin(container); // see explanation below
}
You're surprised to see that non-member cbegindoesn't call member cbegin, aren't you? So was I. But follow the logic. This cbegintemplate accepts any type of argument representing a container-like data structure, C, and it accesses this argument through its reference-to -constparameter, container. If Cis a conventional container type (e.g., a std::vector), containerwill be a reference to a constversion of that container (e.g., a const std::vector&). Invoking the non- member beginfunction (provided by C++11) on a constcontainer yields a const_iterator, and that iterator is what this template returns. The advantage of implementing things this way is that it works even for containers that offer a beginmember function (which, for containers, is what C++11's non-member begincalls), but fail to offer a cbeginmember. You can thus use this non-member cbeginon containers that directly support only begin.
This template also works if C is a built-in array type. In that case, containerbecomes a reference to a constarray. C++11 provides a specialized version of non-member beginfor arrays that returns a pointer to the array's first element. The elements of a constarray are const, so the pointer that non-member beginreturns for a constarray is a pointer-to- const, and a pointer-to- constis, in fact, a const_iteratorfor an array. (For insight into how a template can be specialized for built-in arrays, consult Item 1's discussion of type deduction in templates that take reference parameters to arrays.)
But back to basics. The point of this Item is to encourage you to use const_iterators whenever you can. The fundamental motivation — using constwhenever it's meaningful — predates C++11, but in C++98, it simply wasn't practical when working with iterators. In C++11, it's eminently practical, and C++14 tidies up the few bits of unfinished business that C++11 left behind.
Things to Remember
• 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: Declare functions noexceptif they won't emit exceptions.
In C++98, exception specifications were rather temperamental beasts. You had to summarize the exception types a function might emit, so if the function's implementation was modified, the exception specification might require revision, too. Changing an exception specification could break client code, because callers might be dependent on the original exception specification. Compilers typically offered no help in maintaining consistency among function implementations, exception specifications, and client code. Most programmers ultimately decided that C++98 exception specifications weren't worth the trouble.
During work on C++11, a consensus emerged that the truly meaningful information about a function's exception-emitting behavior was whether it had any. Black or white, either a function might emit an exception or it guaranteed that it wouldn't. This maybe-or-never dichotomy forms the basis of C++11's exception specifications, which essentially replace C++98's. (C++98-style exception specifications remain valid, but they're deprecated.) In C++11, unconditional noexceptis for functions that guarantee they won't emit exceptions.
Whether a function should be so declared is a matter of interface design. The exception-emitting behavior of a function is of key interest to clients. Callers can query a function's noexceptstatus, and the results of such a query can affect the exception safety or efficiency of the calling code. As such, whether a function is noexceptis as important a piece of information as whether a member function is const. Failure to declare a function noexceptwhen you know that it won't emit an exception is simply poor interface specification.
But there's an additional incentive to apply noexceptto functions that won't produce exceptions: it permits compilers to generate better object code. To understand why, it helps to examine the difference between the C++98 and C++11 ways of saying that a function won't emit exceptions. Consider a function fthat promises callers they'll never receive an exception. The two ways of expressing that are:
int f(int x) throw(); // no exceptions from f: C++98 style
int f(int x) noexcept; // no exceptions from f: C++11 style
If, at runtime, an exception leaves f, f's exception specification is violated. With the C++98 exception specification, the call stack is unwound to f's caller, and, after some actions not relevant here, program execution is terminated. With the C++11 exception specification, runtime behavior is slightly different: the stack is only possibly unwound before program execution is terminated.
The difference between unwinding the call stack and possibly unwinding it has a surprisingly large impact on code generation. In a noexceptfunction, optimizers need not keep the runtime stack in an unwindable state if an exception would propagate out of the function, nor must they ensure that objects in a noexceptfunction are destroyed in the inverse order of construction should an exception leave the function. Functions with “ throw()” exception specifications lack such optimization flexibility, as do functions with no exception specification at all. The situation can be summarized this way:
RetType function ( params ) noexcept; // most optimizable
RetType function ( params ) throw(); // less optimizable
RetType function ( params ); // less optimizable
This alone is sufficient reason to declare functions noexceptwhenever you know they won't produce exceptions.
For some functions, the case is even stronger. The move operations are the preeminent example. Suppose you have a C++98 code base making use of a std::vector. Widgets are added to the std::vectorfrom time to time via push_back:
std::vector vw;
…
Widget w;
… // work with w
vw.push_back(w); // add w to vw
…
Assume this code works fine, and you have no interest in modifying it for C++11. However, you do want to take advantage of the fact that C++11's move semantics can improve the performance of legacy code when move-enabled types are involved. You therefore ensure that Widgethas move operations, either by writing them yourself or by seeing to it that the conditions for their automatic generation are fulfilled (see Item 17).
When a new element is added to a std::vector, it's possible that the std::vectorlacks space for it, i.e., that the std::vector's size is equal to its capacity. When that happens, the std::vectorallocates a new, larger, chunk of memory to hold its elements, and it transfers the elements from the existing chunk of memory to the new one. In C++98, the transfer was accomplished by copying each element from the old memory to the new memory, then destroying the objects in the old memory. This approach enabled push_backto offer the strong exception safety guarantee: if an exception was thrown during the copying of the elements, the state of the std::vectorremained unchanged, because none of the elements in the old memory were destroyed until all elements had been successfully copied into the new memory.
Читать дальше