Wie viele Leute in diesen Tagen habe ich die verschiedenen Funktionen von C ++ 11 ausprobiert. Einer meiner Favoriten ist der "Range-Based for Loops".
Ich verstehe das:
for(Type& v : a) { ... }
Ist äquivalent zu:
for(auto iv = begin(a); iv != end(a); ++iv)
{
Type& v = *iv;
...
}
Und das begin()
kehrt einfach a.begin()
für Standardcontainer zurück.
Aber was ist, wenn ich meinen benutzerdefinierten Typ "bereichsbasiert für Schleife" aktivieren möchte ?
Soll ich mich nur spezialisieren begin()
und end()
?
Wenn mein benutzerdefinierter Typ zum Namespace gehört xml
, sollte ich xml::begin()
oder definieren std::begin()
?
Kurz gesagt, was sind die Richtlinien, um das zu tun?
c++
for-loop
c++11
customization
ereOn
quelle
quelle
begin/end
oder einen Freund zu definieren, statisch oder freibegin/end
. Seienfor( auto x : range<float>(0,TWO_PI, 0.1F) ) { ... }
. Ich bin gespannt, wie Sie die Tatsache umgehen, dass "Operator! = ()" Schwer zu definieren ist. Und was ist*__begin
in diesem Fall mit der Dereferenzierung ( )? Ich denke, es wäre ein großartiger Beitrag, wenn uns jemand zeigen würde, wie das gemacht wird!Antworten:
Der Standard wurde geändert, seit die Frage (und die meisten Antworten) in der Lösung dieses Fehlerberichts veröffentlicht wurden .
Es gibt zwei Möglichkeiten, wie Sie eine
for(:)
Schleife für Ihren Typ verwendenX
können:Erstellen Sie Mitglied
X::begin()
undX::end()
dass die Rückkehr etwas , das wie ein Iterator wirktErstellen Sie eine freie Funktion
begin(X&)
undend(X&)
dass die Rückkehr etwas , das wie ein Iterator fungiert, im gleichen Namensraum wie Ihre ArtX
.¹Und ähnlich für
const
Variationen. Dies funktioniert sowohl bei Compilern, die die Änderungen des Fehlerberichts implementieren, als auch bei Compilern, die dies nicht tun.Die zurückgegebenen Objekte müssen keine Iteratoren sein. Die
for(:)
Schleife wird im Gegensatz zu den meisten Teilen des C ++ - Standards so spezifiziert , dass sie wie folgt erweitert wird :wird:
wobei die Variablen, die mit beginnen,
__
nur zur Darstellung dienenbegin_expr
undend_expr
die Magie ist, diebegin
/end
.² aufruftDie Anforderungen an den Rückgabewert für Anfang / Ende sind einfach: Sie müssen vorab überladen
++
, sicherstellen, dass die Initialisierungsausdrücke gültig sind, binär!=
, die in einem booleschen Kontext verwendet werden können, unär*
, was etwas zurückgibt, mit dem Sie initialisieren könnenrange_declaration
, und eine Öffentlichkeit verfügbar machen Zerstörer.Dies auf eine Weise zu tun, die nicht mit einem Iterator kompatibel ist, ist wahrscheinlich eine schlechte Idee, da zukünftige Iterationen von C ++ möglicherweise relativ unbekümmert sind, wenn Sie Ihren Code brechen, wenn Sie dies tun.
Abgesehen davon ist es ziemlich wahrscheinlich, dass eine zukünftige Überarbeitung des Standards die
end_expr
Rückgabe eines anderen Typs als ermöglichtbegin_expr
. Dies ist insofern nützlich, als es eine "Lazy-End" -Auswertung (wie das Erkennen einer Nullterminierung) ermöglicht, die leicht zu optimieren ist, um so effizient wie eine handgeschriebene C-Schleife zu sein, und andere ähnliche Vorteile.¹ Beachten Sie, dass
for(:)
Schleifen alle temporärenauto&&
Elemente in einer Variablen speichern und als l-Wert an Sie übergeben. Sie können nicht erkennen, ob Sie über einen temporären (oder einen anderen Wert) iterieren. Eine solche Überlastung wird von einerfor(:)
Schleife nicht aufgerufen . Siehe [stmt.ranged] 1.2-1.3 von n4527.² entweder den Anruf
begin
/ eineend
Methode oder ADL-only lookup freier Funktionbegin
/end
, oder Magie für C-style - Array - Unterstützung. Beachten Sie, dass diesstd::begin
nur aufgerufen wird, wennrange_expression
ein Objekt vom Typ innamespace std
dasselbe zurückgegeben wird oder von diesem abhängig ist.Im c ++ 17 Der Range-for-Ausdruck wurde aktualisiert
mit den Arten von
__begin
und__end
wurden entkoppelt.Dadurch kann der Enditerator nicht vom selben Typ wie begin sein. Ihr Enditeratortyp kann ein "Sentinel" sein, der nur
!=
mit dem Anfangsiteratortyp unterstützt wird.Ein praktisches Beispiel dafür , warum dies nützlich ist , dass Ihr Ende kann Iterator „überprüfen Sie Ihre lesen ,
char*
um zu sehen , ob es weist auf'0'
“ , wenn==
mit einchar*
. Auf diese Weise kann ein C ++ - Bereichsausdruck optimalen Code generieren, wenn er über einen nullterminiertenchar*
Puffer iteriert .Live-Beispiel in einem Compiler ohne vollständige C ++ 17-Unterstützung;
for
Schleife manuell erweitert.quelle
begin
undend
Funktionen erhält, als dies im normalen Code verfügbar ist. Vielleicht könnten sie dann sehr spezialisiert sein, um sich anders zu verhalten (dh schneller, indem sie das Endargument ignorieren, um die größtmöglichen Optimierungsoptimierungen zu erzielen). Aber ich bin nicht gut genug mit Namespaces, um sicher zu sein, wie das geht.begin(X&&)
. Das Temporär wird in der Luft vonauto&&
in einem bereichsbasierten forbegin
angehalten und immer mit einem lvalue (__range
) aufgerufen .Ich schreibe meine Antwort, weil einige Leute mit einfachen Beispielen aus dem wirklichen Leben ohne STL-Includes zufriedener sein könnten.
Ich habe aus irgendeinem Grund meine eigene einfache Datenarray-Implementierung und wollte den Bereich verwenden, der auf der Schleife basiert. Hier ist meine Lösung:
Dann das Anwendungsbeispiel:
quelle
const
Rückgabequalifikationsmerkmal für zu entfernenconst DataType& operator*()
und den Benutzer wählen zu lassen, obconst auto&
oderauto&
?Der relevante Teil der Norm ist 6.5.4 / 1:
Sie können also Folgendes tun:
begin
undend
Elementfunktionenbegin
und geben Sieend
Funktionen frei, die von ADL gefunden werden (vereinfachte Version: Fügen Sie sie in denselben Namespace wie die Klasse ein).std::begin
undstd::end
std::begin
Ruft diebegin()
Member-Funktion trotzdem auf. Wenn Sie also nur eine der oben genannten Funktionen implementieren, sollten die Ergebnisse unabhängig von der gewählten Funktion gleich sein. Das sind die gleichen Ergebnisse für Fernkampf-basierte for-Schleifen und das gleiche Ergebnis für bloßen Sterblichen Code, der keine eigenen Regeln für die Auflösung magischer Namen hat, also nurusing std::begin;
gefolgt von einem unqualifizierten Aufruf vonbegin(a)
.Wenn Sie jedoch die Elementfunktionen und die ADL-Funktionen implementieren , sollten bereichsbasierte for-Schleifen die Elementfunktionen aufrufen, während bloße Sterbliche die ADL-Funktionen aufrufen. Stellen Sie am besten sicher, dass sie in diesem Fall dasselbe tun!
Wenn das , was Sie implementiert die Container - Schnittstelle schreiben, dann wird es haben
begin()
undend()
Elementfunktionen bereits, was ausreichend sein sollte. Wenn es sich um einen Bereich handelt, der kein Container ist (was eine gute Idee wäre, wenn er unveränderlich ist oder wenn Sie die Größe im Voraus nicht kennen), können Sie frei wählen.Beachten Sie, dass Sie bei den von Ihnen bereitgestellten Optionen nicht überladen dürfen
std::begin()
. Sie können Standardvorlagen für einen benutzerdefinierten Typ spezialisieren. Abgesehen davon ist das Hinzufügen von Definitionen zum Namespace std ein undefiniertes Verhalten. Die Spezialisierung von Standardfunktionen ist jedoch schon deshalb eine schlechte Wahl, da Sie aufgrund der fehlenden Spezialisierung auf Teilfunktionen nur für eine einzelne Klasse und nicht für eine Klassenvorlage arbeiten können.quelle
!=
, Präfix++
und unär*
. Es ist wahrscheinlich unklug zu implementierenbegin()
undend()
Member - Funktionen oder Dritt ADL Funktionen dass die Rückkehr etwas anderes als ein Iterator, aber ich denke , dass es legal ist.std::begin
Ich glaube, UB ist darauf spezialisiert , einen Nicht-Iterator zurückzugeben.Soweit ich weiß, ist das genug. Sie müssen auch sicherstellen, dass das Inkrementieren des Zeigers vom Anfang bis zum Ende erfolgt.
Das nächste Beispiel (es fehlt die const-Version von Anfang und Ende) wird kompiliert und funktioniert einwandfrei.
Hier ist ein weiteres Beispiel mit Anfang / Ende als Funktionen. Sie müssen sich aufgrund von ADL im selben Namespace wie die Klasse befinden:
quelle
return v + 10
.&v[10]
dereferenziert den Speicherort direkt hinter dem Array.Falls Sie die Iteration einer Klasse direkt mit ihrem
std::vector
oder unterstützen möchtenstd::map
Mitglied unterstützen , finden Sie hier den Code dafür:quelle
const_iterator
auch aufauto
(C ++ 11) -kompatible Weise über usw. zugegriffen werdencbegin
kanncend
Hier teile ich das einfachste Beispiel für das Erstellen eines benutzerdefinierten Typs, der mit " bereichsbasierter for-Schleife " funktioniert :
Hoffe, es wird für einige unerfahrene Entwickler wie mich hilfreich sein: p :)
Danke.
quelle
end()
Funktion selbst dereferenziert offensichtlich keinen falschen Speicherort, da sie nur die 'Adresse' dieses Speicherorts verwendet. Das Hinzufügen eines zusätzlichen Elements würde bedeuten, dass Sie mehr Speicher benötigen würden, und die Verwendungyour_iterator::end()
in einer Weise, die den Wert dereferenziert, würde ohnehin nicht mit anderen Iteratoren funktionieren, da diese auf dieselbe Weise erstellt werden.return &data[sizeofarray]
IMHO sollte es nur die Adressdaten + Größe des Arrays zurückgeben, aber was weiß ich,data + sizeofarray
wäre der richtige Weg, dies zu schreiben.Chris Redfords Antwort funktioniert (natürlich) auch für Qt-Container. Hier ist eine Anpassung (beachten Sie, dass ich
constBegin()
jeweils aconstEnd()
von den const_iterator-Methoden zurückgebe):quelle
Ich möchte einige Teile der Antwort von @Steve Jessop näher erläutern, für die ich zunächst nichts verstanden habe. Ich hoffe es hilft.
https://en.cppreference.com/w/cpp/language/range-for :
Bei einer bereichsbasierten for-Schleife werden zuerst Elementfunktionen ausgewählt.
Aber für
ADL-Funktionen werden zuerst ausgewählt.
Beispiel:
quelle