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 cbegin
doesn't call member cbegin
, aren't you? So was I. But follow the logic. This cbegin
template accepts any type of argument representing a container-like data structure, C
, and it accesses this argument through its reference-to -const
parameter, container
. If C
is a conventional container type (e.g., a std::vector
), container
will be a reference to a const
version of that container (e.g., a const std::vector&
). Invoking the non- member begin
function (provided by C++11) on a const
container 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 begin
member function (which, for containers, is what C++11's non-member begin
calls), but fail to offer a cbegin
member. You can thus use this non-member cbegin
on containers that directly support only begin
.
This template also works if C is a built-in array type. In that case, container
becomes a reference to a const
array. C++11 provides a specialized version of non-member begin
for arrays that returns a pointer to the array's first element. The elements of a const
array are const
, so the pointer that non-member begin
returns for a const
array is a pointer-to- const
, and a pointer-to- const
is, in fact, a const_iterator
for 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_iterator
s whenever you can. The fundamental motivation — using const
whenever 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_iterator
s to iterator
s.
• In maximally generic code, prefer non-member versions of begin
, end
, rbegin
, etc., over their member function counterparts.
Item 14: Declare functions noexcept
if 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 noexcept
is 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 noexcept
status, and the results of such a query can affect the exception safety or efficiency of the calling code. As such, whether a function is noexcept
is as important a piece of information as whether a member function is const
. Failure to declare a function noexcept
when you know that it won't emit an exception is simply poor interface specification.
But there's an additional incentive to apply noexcept
to 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 f
that 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 noexcept
function, 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 noexcept
function 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 noexcept
whenever 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
. Widget
s are added to the std::vector
from 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 Widget
has 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::vector
lacks space for it, i.e., that the std::vector
's size is equal to its capacity. When that happens, the std::vector
allocates 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_back
to offer the strong exception safety guarantee: if an exception was thrown during the copying of the elements, the state of the std::vector
remained unchanged, because none of the elements in the old memory were destroyed until all elements had been successfully copied into the new memory.
Читать дальше