Such unintentional type mismatches can be auto
ed away:
for (const auto& p : m) {
… // as before
}
This is not only more efficient, it's also easier to type. Furthermore, this code has the very attractive characteristic that if you take p's address, you're sure to get a pointer to an element within m
. In the code not using auto, you'd get a pointer to a temporary object — an object that would be destroyed at the end of the loop iteration.
The last two examples — writing unsigned
when you should have written std::vector::size_type
and writing std::pair
when you should have written s td::pair
— demonstrate how explicitly specifying types can lead to implicit conversions that you neither want nor expect. If you use auto
as the type of the target variable, you need not worry about mismatches between the type of variable you're declaring and the type of the expression used to initialize it.
There are thus several reasons to prefer auto over explicit type declarations. Yet auto
isn't perfect. The type for each auto
variable is deduced from its initializing expression, and some initializing expressions have types that are neither anticipated nor desired. The conditions under which such cases arise, and what you can do about them, are discussed in Items 2and 6, so I won't address them here. Instead, I'll turn my attention to a different concern you may have about using auto in place of traditional type declarations: the readability of the resulting source code.
First, take a deep breath and relax. auto
is an option, not a mandate. If, in your professional judgment, your code will be clearer or more maintainable or in some other way better by using explicit type declarations, you're free to continue using them. But bear in mind that C++ breaks no new ground in adopting what is generally known in the programming languages world as type inference . Other statically typed procedural languages (e.g., C#, D, Scala, Visual Basic) have a more or less equivalent feature, to say nothing of a variety of statically typed functional languages (e.g., ML, Haskell, OCaml, F#, etc.). In part, this is due to the success of dynamically typed languages such as Perl, Python, and Ruby, where variables are rarely explicitly typed. The software development community has extensive experience with type inference, and it has demonstrated that there is nothing contradictory about such technology and the creation and maintenance of large, industrial-strength code bases.
Some developers are disturbed by the fact that using auto
eliminates the ability to determine an object's type by a quick glance at the source code. However, IDEs' ability to show object types often mitigates this problem (even taking into account the IDE type-display issues mentioned in Item 4), and, in many cases, a somewhat abstract view of an object's type is just as useful as the exact type. It often suffices, for example, to know that an object is a container or a counter or a smart pointer, without knowing exactly what kind of container, counter, or smart pointer it is. Assuming well-chosen variable names, such abstract type information should almost always be at hand.
The fact of the matter is that writing types explicitly often does little more than introduce opportunities for subtle errors, either in correctness or efficiency or both. Furthermore, auto
types automatically change if the type of their initializing expression changes, and that means that some refactorings are facilitated by the use of auto
. For example, if a function is declared to return an int
, but you later decide that a long
would be better, the calling code automatically updates itself the next time you compile if the results of calling the function are stored in auto
variables. If the results are stored in variables explicitly declared to be int
, you'll need to find all the call sites so that you can revise them.
Things to Remember
• auto
variables must be initialized, are generally immune to type mismatches that can lead to portability or efficiency problems, can ease the process of refactoring, and typically require less typing than variables with explicitly specified types.
• auto
-typed variables are subject to the pitfalls described in Items 2and 6.
Item 6: Use the explicitly typed initializer idiom when auto
deduces undesired types.
Item 5explains that using auto
to declare variables offers a number of technical advantages over explicitly specifying types, but sometimes auto
's type deduction zigs when you want it to zag. For example, suppose I have a function that takes a Widget
and returns a std::vector
, where each bool
indicates whether the Widget
offers a particular feature:
std::vector features(const Widget& w);
Further suppose that bit 5 indicates whether the Widget
has high priority. We can thus write code like this:
Widget w;
…
bool highPriority = features(w)[5]; // is w high priority?
…
processWidget(w, highPriority); // process w in accord
// with its priority
There's nothing wrong with this code. It'll work fine. But if we make the seemingly innocuous change of replacing the explicit type for highPriority
with auto
,
autohighPriority = features(w)[5]; // is w high priority?
the situation changes. All the code will continue to compile, but its behavior is no longer predictable:
processWidget(w, highPriority); // undefined behavior!
As the comment indicates, the call to processWidget
now has undefined behavior. But why? The answer is likely to be surprising. In the code using auto
, the type of highPriority
is no longer bool
. Though std::vector
conceptually holds bool
s, operator[]
for std::vector
doesn't return a reference to an element of the container (which is what std::vector::operator[]
returns for every type except bool
). Instead, it returns an object of type std::vector::reference
(a class nested inside std::vector
).
std::vector::reference
exists because std::vector
is specified to represent its bool
s in packed form, one bit per bool
. That creates a problem for std::vector
's operator[]
, because operator[]
for std::vector
is supposed to return a T&
, but C++ forbids references to bits. Not being able to return a bool&
, operator[]
for std::vector
returns an object that acts like a bool&
. For this act to succeed, std::vector::reference
objects must be usable in essentially all contexts where bool&
s can be. Among the features in std::vector::reference
that make this work is an implicit conversion to bool
. (Not to bool&
, to bool
. To explain the full set of techniques used by std::vector::reference
to emulate the behavior of a bool&
would take us too far afield, so I'll simply remark that this implicit conversion is only one stone in a larger mosaic.)
Читать дальше