Auto und for-Schleifen
Nein, man lernt tatsächlich nie aus. Seit C++11 können wir ja schreiben
vector data{};
...
for(auto e : data) { cout << e; };
Das ist schon ganz schön praktisch gegenüber for(vector it=data.begin();...) { cout << *it; }
, hat aber den großen
Nachteil, dass erstens e
hier oimmer kopiert wird und zweitens Änderungen an e
sich nicht in data
auswirken.
Stimmt, und deshalb erzähle ich auch immer, dass man daran danken sollte, in solchen Schleifen zuerst immer zu prüfen,
ob man stattdessen nicht auto&
oder const auto&
verwenden sollte:
vector<int> data{};
...
for(auto& e : data) { e += 1; };
Das verhindert Kopie und data
wird auch wirklich modifiziert.
Jetzt werde
ich gerade erinnert
, dass das nicht immer reicht. Zum Beispiel kann es keine (echte) Referenz in einen vector<bool>
geben, weil man
einzelne Bits nicht adressieren (also referenzieren) kann. Das gäbe mit auto&
einen Fehler.
Daher, jetzt meine neue Regel: Man probiere es mit auto&&
. Das ist dann der Einsatz der
Meyerschen Universellen Referenzen, soweit
ich das erkennen kann:
vector<int> data{};
...
for(auto&& e : data) { e += 1; };
Im Effekt bekommt man eine &
, wenn nötig und eine &&
, wenn erlaubt. Die genaue Erklärung was passiert, folgt in
einem Detailbeitrag. Bis dahin probiere ich diese neue Merkregel mal aus.
Nachtrag
In einem Vorschlagsdokument des C++-Standards steht das Dilemma beschrieben:
What should we do about this problem? We can teach users to write
for (auto& elem : range)
orfor (const auto& elem : range)
, but they’re
imperfect (for proxies or mutation, respectively), and they require novices to
be introduced to auto and references earlier than otherwise necessary.
As explained immediately below,for (auto&& elem : range)
is superior, but
it’s terrible to teach to novices. Even experienced C++98/03 programmers can
find it difficult to learn how rvalue references, the template argument
deduction tweak, and reference collapsing interact to produce what Scott Meyers
calls “universal references” (see [1]), and auto&& is far less commonly seen
thanT&&
in perfect forwarding.
Andererseits sagt Herb Sutter
in Back To Basics
, dass man auto&&
nicht für lokale Variablen verwenden soll. Da stimme ich im Prinzip auch zu, aber grüble, ob sein
Gesagtes nicht bei diesen auto-for-Schleifen eine Ausnahme sind. Hauptsächlich im Bezug der Einfachheit allerdings.
“STL” gibt etwas mehr Erklärung hier und bezieht auch den kommenden C++-Standard (wahrscheinlich) mit ein, der…
Ausblick
…eine neue Syntax enthält, die genau diese Schreibweise abkürzt: Einfach den gesamten Typen in der
Schleifendeklaration weglassen und der Compiler macht daraus automatisch auto&&
:
vector<int> data{};
...
for(e : data) { e += 1; };
…in der (fernen) Zukunft (vielleicht).
Zusammenfassung
Mit all den Informationen, die ich nun habe, möchte es noch einmal zusammenfassen. Vor allem “STL” (oben) und Howard Hinnant konnten mir Dinge klären
Ab C++17 gilt: Verwende for(e :...)
wann immer möglich.
vector<int> data{};
...
for(e : data) { e += 1; };
Bis dahin, mit C++11 alleine, gilt:
In 99.9% der Fälle und somit für alle Entwickler bis zum braunen Gürtel gilt (immernoch): Verwende for(auto &x: ...)
oder for(const auto &x: ...)
wenn Du kannst und kopiere nur wenn nötig:
vector<int> data{};
...
for(auto &e : data) { e += 1; };
...
for(const auto &e : data) { cout << e; };
Entwickler mit dem schwarzen Gürtel, und/oder innerhalb einer Bibliothek mit einem Funktionstemplate für den Typ
von data
, kann es sinnvoll sein for(auto &&x: ...)
zu verwenden:
vector<int> data{};
template
void func(T&& data) {
...
for(auto &&e : data) { e += 1; };
...
}
Denn ob RValue-Ref oder LValue-Ref ist nur dann relevant, wenn data
für seine Iteratoren (temporäre) Proxy-Objekte
benötigt (wie vector<bool>
). Selbst wenn man dieses Konstrukt außerhalb eines Funktionstemplate verwendet, wird
man &&
nicht brauchen, sondern kann sich bewusst zwischen ~[const]~ auto& und [const] auto
entscheiden.