Things to Remember
• Declare overriding functions override
.
• Member function reference qualifiers make it possible to treat lvalue and rvalue objects ( *this
) differently.
Item 13: Prefer const_iterator
s to iterator
s.
const_iterator
s are the STL equivalent of pointers-to- const
. They point to values that may not be modified. The standard practice of using const
whenever possible dictates that you should use const_iterator
s any time you need an iterator, yet have no need to modify what the iterator points to.
That's as true for C++98 as for C++11, but in C++98, const_iterator
s had only halfhearted support. It wasn't that easy to create them, and once you had one, the ways you could use it were limited. For example, suppose you want to search a std::vector
for the first occurrence of 1983 (the year “C++” replaced “C with Classes” as the name of the programming language), then insert the value 1998 (the year the first ISO C++ Standard was adopted) at that location. If there's no 1983 in the vector, the insertion should go at the end of the vector. Using iterator
s in C++98, that was easy:
std::vector values;
…
std::vector::iterator it =
std::find(values.begin(),values.end(), 1983);
values.insert(it, 1998);
But iterator
s aren't really the proper choice here, because this code never modifies what an iterator
points to. Revising the code to use const_iterator
s should be trivial, but in C++98, it was anything but. Here's one approach that's conceptually sound, though still not correct:
typedef std::vector::iterator IterT; // type-
typedef std::vector::const_iterator ConstIterT; // defs
std::vector values;
…
ConstIterT ci =
std::find( static_cast(values.begin() ), // cast
static_cast(values.end() ), // cast
1983);
values.insert( static_cast(ci), 1998); // may not
// compile; see
// below
The typedef
s aren't required, of course, but they make the casts in the code easier to write. (If you're wondering why I'm showing typedef
s instead of following the advice of Item 9to use alias declarations, it's because this example shows C++98 code, and alias declarations are a feature new to C++11.)
The casts in the call to std::find
are present because values
is a non- const
container and in C++98, there was no simple way to get a const_iterator
from a non- const
container. The casts aren't strictly necessary, because it was possible to get const_iterator
s in other ways (e.g., you could bind values
to a reference-to- const
variable, then use that variable in place of values
in your code), but one way or another, the process of getting const_iterator
s to elements of a non- const
container involved some amount of contorting.
Once you had the const_iterator
s, matters often got worse, because in C++98, locations for insertions (and erasures) could be specified only by iterator
s. const_iterator
s weren't acceptable. That's why, in the code above, I cast the const_iterator
(that I was so careful to get from std::find
) into an iterator
: passing a const_iterator
to insert
wouldn't compile.
To be honest, the code I've shown might not compile, either, because there's no portable conversion from a const_iterator
to an iterator
, not even with a static_cast
. Even the semantic sledgehammer known as reinterpret_cast
can't do the job. (That's not a C++98 restriction. It's true in C++11, too. const_iterator
s simply don't convert to iterator
s, no matter how much it might seem like they should.) There are some portable ways to generate i terator
s that point where const_iterator
s do, but they're not obvious, not universally applicable, and not worth discussing in this book. Besides, I hope that by now my point is clear: const_iterator
s were so much trouble in C++98, they were rarely worth the bother. At the end of the day, developers don't use const
whenever possible , they use it whenever practical , and in C++98, const_iterator
s just weren't very practical.
All that changed in C++11. Now const_iterator
s are both easy to get and easy to use. The container member functions cbegin
and cend
produce const_iterator
s, even for non- const
containers, and STL member functions that use iterators to identify positions (e.g., insert and erase
) actually use const_iterator
s. Revising the original C++98 code that uses iterator
s to use const_iterator
s in C++11 is truly trivial:
std::vector values; // as before
…
autoit = // use cbegin
std::find(values. cbegin(),values. cend(), 1983); // and cend
values.insert(it, 1998);
Now that's code using const_iterator
s that's practical!
About the only situation in which C++11's support for const_iterator
s comes up a bit short is when you want to write maximally generic library code. Such code takes into account that some containers and container-like data structures offer begin
and end
(plus cbegin
, cend
, rbegin
, etc.) as non-member functions, rather than members. This is the case for built-in arrays, for example, and it's also the case for some third-party libraries with interfaces consisting only of free functions. Maximally generic code thus uses non-member functions rather than assuming the existence of member versions.
For example, we could generalize the code we've been working with into a findAndInsert
template as follows:
template
void findAndInsert(C& container, // in container, find
const V& targetVal, // first occurrence
const V& insertVal) // of targetVal, then
{ // insert insertVal
using std::cbegin; // there
using std::cend;
auto it = std::find( cbegin(container), // non-member cbegin
cend(container), // non-member cend
targetVal);
container.insert(it, insertVal);
}
This works fine in C++14, but, sadly, not in C++11. Through an oversight during standardization, C++11 added the non-member functions begin
and end
, but it failed to add cbegin
, cend
, rbegin
, rend
, crbegin
, and crend
. C++14 rectifies that oversight.
If you're using C++11, you want to write maximally generic code, and none of the libraries you're using provides the missing templates for non-member cbegin
and friends, you can throw your own implementations together with ease. For example, here's an implementation of non-member cbegin
:
Читать дальше