Wenn ich einen Iterator in einen Vektor habe a
, dann bewege ich einen Vektor b
aus oder bewege ihn zu. Zeigt a
dieser Iterator immer noch auf dasselbe Element (jetzt im Vektor b
)? Folgendes meine ich im Code:
#include <vector>
#include <iostream>
int main(int argc, char *argv[])
{
std::vector<int>::iterator a_iter;
std::vector<int> b;
{
std::vector<int> a{1, 2, 3, 4, 5};
a_iter = a.begin() + 2;
b = std::move(a);
}
std::cout << *a_iter << std::endl; // Is a_iter valid here?
return 0;
}
Ist es a_iter
noch gültig, seit a
es verschoben wurde b
, oder ist der Iterator durch das Verschieben ungültig? Als Referenz werden std::vector::swap
Iteratoren nicht ungültig .
a_iter
jetzt auf ein Element verwiesen wird,b
nachdema
es verschoben wurde.vectors
implementiert werden).Antworten:
Es ist zwar vernünftig anzunehmen, dass
iterator
s nach a noch gültig sindmove
, aber ich glaube nicht, dass der Standard dies tatsächlich garantiert. Daher befinden sich die Iteratoren nach dem in einem undefinierten Zustandmove
.Es gibt keine Referenz, die ich im Standard finden kann, die ausdrücklich besagt, dass Iteratoren, die vor a existierten
move
, nach dem noch gültig sindmove
.An der Oberfläche scheint es durchaus sinnvoll zu sein , anzunehmen , dass ein
iterator
wird typischerweise als Zeiger in der kontrollierten Sequenz implementiert. Wenn dies der Fall ist, sind die Iteratoren nach dem noch gültigmove
.Die Implementierung eines
iterator
ist jedoch implementierungsdefiniert. Das heißt, solange dieiterator
Plattform auf einer bestimmten Plattform die Anforderungen des Standards erfüllt, kann sie auf jede Art und Weise implementiert werden. Es könnte theoretisch als eine Kombination eines Zeigers zurück auf dievector
Klasse zusammen mit einem Index implementiert werden. Wenn das ist der Fall, dann würden die Iteratoren nach der ungültigmove
.Ob ein
iterator
tatsächlich auf diese Weise implementiert wird oder nicht , spielt keine Rolle. Es könnte auf diese Weise implementiert werden. Ohne eine spezielle Garantie des Standards, dassmove
Postiteratoren weiterhin gültig sind, können Sie nicht davon ausgehen, dass dies der Fall ist . Denken Sie auch daran, dass es eine solche Garantie für Iteratoren nach a gibtswap
. Dies wurde speziell aus dem vorherigen Standard klargestellt. Vielleicht war es einfach ein Versehen des Std-Ausschusses, nach a keine ähnliche Klarstellung für Iteratoren vorzunehmenmove
, aber auf jeden Fall gibt es keine solche Garantie.Daher können Sie nicht davon ausgehen, dass Ihre Iteratoren nach a noch gut sind
move
.BEARBEITEN:
23.2.1 / 11 im Entwurf n3242 besagt, dass:
Dies könnte zu dem Schluss führen, dass die Iteratoren nach a gültig sind
move
, aber ich bin anderer Meinung. In Ihrem Beispielcodea_iter
war ein Iterator in dervector
a
. Nach demmove
, dieser Container,a
wurde sicherlich geändert. Mein Fazit ist, dass die obige Klausel in diesem Fall nicht gilt.quelle
Ich denke, die Bearbeitung, die die Verschiebungskonstruktion geändert hat, um die Zuweisung zu verschieben, ändert die Antwort.
Zumindest wenn ich Tabelle 96 richtig lese, wird die Komplexität für die Bewegungskonstruktion als "Anmerkung B" angegeben, was für alles andere als eine konstante Komplexität ist
std::array
. Die Komplexität für die Zugzuweisung wird jedoch als lineares gegeben.Daher hat die Verschiebungskonstruktion im Wesentlichen keine andere Wahl, als den Zeiger von der Quelle zu kopieren. In diesem Fall ist schwer zu erkennen, wie die Iteratoren ungültig werden können.
Für die Bewegungszuweisung bedeutet dies jedoch, dass dies aufgrund der linearen Komplexität möglich ist , dass einzelne Elemente von der Quelle zum Ziel verschoben werden können. In diesem Fall werden die Iteratoren mit ziemlicher Sicherheit ungültig.
Die Möglichkeit der Bewegungszuweisung von Elementen wird durch die Beschreibung verstärkt: "Alle vorhandenen Elemente eines Zuges werden entweder einem Zug zugewiesen oder zerstört". Der "zerstörte" Teil würde dem Zerstören des vorhandenen Inhalts und dem "Stehlen" des Zeigers von der Quelle entsprechen - aber die "zugewiesene Verschiebung" würde stattdessen das Verschieben einzelner Elemente von der Quelle zum Ziel anzeigen.
quelle
propagate_on_container_move_assignment
, wenn dies wahr ist. Wenn dies nicht der Fall ist und die Allokatoren nicht gleich sind, kann der vorhandene Speicher nicht verschoben werden mögliche Neuzuweisung und jedes Element wird einzeln verschoben.Da es nichts gibt, was einen Iterator davon abhält, einen Verweis oder Zeiger auf den Originalcontainer zu behalten, würde ich sagen, dass Sie sich nicht darauf verlassen können, dass die Iteratoren gültig bleiben, es sei denn, Sie finden eine explizite Garantie im Standard.
quelle
Unless otherwise specified (either explicitly or by defining a function in terms of other functions), invoking a container member function or passing a container as an argument to a library function shall not invalidate iterators to, or change the values of, objects within that container.
[container.requirements.general]vector::swap
eine konstante Zeit und auch keine ungültigen Iteratoren nicht verhindernvector::iterator
, dass ein Zeiger auf den ursprünglichen Container enthalten ist?tl; dr: Ja, das Verschieben von a macht
std::vector<T, A>
die Iteratoren möglicherweise ungültigDer häufigste Fall (mit vorhanden
std::allocator
) ist, dass keine Ungültigmachung stattfindet, es jedoch keine Garantie gibt und das Wechseln von Compilern oder sogar das nächste Compiler-Update dazu führen kann, dass sich Ihr Code falsch verhält, wenn Sie sich darauf verlassen, dass Ihre Implementierung die Iteratoren derzeit nicht ungültig macht.Zuweisung bei Umzug :
Die Frage, ob
std::vector
Iteratoren nach der Verschiebungszuweisung tatsächlich gültig bleiben können, hängt mit der Allokatorerkennung der Vektorvorlage zusammen und hängt vom Allokatortyp (und möglicherweise den jeweiligen Instanzen davon) ab.In jeder Implementierung, die ich gesehen habe, macht die Verschiebungszuweisung einer
std::vector<T, std::allocator<T>>
1 Iteratoren oder Zeiger nicht ungültig. Es gibt jedoch ein Problem, wenn es darum geht, dies zu nutzen, da der Standard einfach nicht garantieren kann, dass Iteratoren für eine Verschiebungszuweisung einerstd::vector
Instanz im Allgemeinen gültig bleiben , da der Container Allokator-fähig ist.Benutzerdefinierte Zuweiser haben möglicherweise den Status. Wenn sie sich bei der Zuweisung von Verschiebungen nicht ausbreiten und nicht gleich vergleichen, muss der Vektor Speicher für die verschobenen Elemente mithilfe seines eigenen Zuweisers zuweisen.
Lassen:
std::vector<T, A> a{/*...*/}; std::vector<T, A> b; b = std::move(a);
Nun wenn
std::allocator_traits<A>::propagate_on_container_move_assignment::value == false &&
std::allocator_traits<A>::is_always_equal::value == false &&
( möglicherweise ab c ++ 17 )a.get_allocator() != b.get_allocator()
Dann
b
wird neuer Speicher zugewiesen und Elemente von verschobena
nacheinander in diesen Speicher , wodurch alle Iteratoren, Zeiger und Referenzen ungültig werden.Der Grund ist, dass die Erfüllung der obigen Bedingung 1 die Zuordnung des Allokators bei Containerumzug verbietet. Daher müssen wir uns mit zwei verschiedenen Instanzen des Allokators befassen. Wenn diese beiden Allokatorobjekte jetzt weder immer gleich ( 2. ) noch tatsächlich gleich vergleichen, haben beide Allokatoren einen unterschiedlichen Status. Ein Allokator ist
x
möglicherweise nicht in der Lage, den Speicher eines anderen Allokatorsy
mit einem anderen Status freizugeben, und daher kann ein Container mit Allokatorx
nicht einfach Speicher von einem Container stehlen, der seinen Speicher über zugewiesen haty
.Wenn sich der Allokator bei der Verschiebungszuweisung ausbreitet oder wenn beide Allokatoren gleich sind, wird eine Implementierung sehr wahrscheinlich nur
b
eigenea
Daten erstellen, da sie sicher sein kann, den Speicher ordnungsgemäß freizugeben.1 :
std::allocator_traits<std::allocator<T>>::propagate_on_container_move_assignment
undstd::allocator_traits<std::allocator<T>>::is_always_equal
beide sind typdefs fürstd::true_type
(für alle nicht spezialisiertenstd::allocator
).On-Move-Bau :
std::vector<T, A> a{/*...*/}; std::vector<T, A> b(std::move(a));
Der Verschiebungskonstruktor eines Allokator-fähigen Containers verschiebt seine Allokator-Instanz aus der Allokator-Instanz des Containers, aus dem der aktuelle Ausdruck verschoben wird. Auf diese Weise wird die ordnungsgemäße Freigabefähigkeit sichergestellt, und der Speicher kann (und wird) gestohlen werden, da die Bewegungskonstruktion (mit Ausnahme von
std::array
) eine konstante Komplexität aufweisen muss.Hinweis: Es gibt noch keine Garantie dafür, dass Iteratoren auch für Verschiebungskonstruktionen gültig bleiben.
Beim Tausch :
Es ist einfach zu fordern, dass die Iteratoren von zwei Vektoren nach einem Swap gültig bleiben (jetzt nur auf den jeweiligen Swap-Container zeigen), da das Swapping nur dann ein definiertes Verhalten aufweist, wenn
std::allocator_traits<A>::propagate_on_container_swap::value == true ||
a.get_allocator() == b.get_allocator()
Wenn sich die Allokatoren beim Auslagern nicht ausbreiten und wenn sie nicht gleich sind, ist das Auslagern der Container in erster Linie ein undefiniertes Verhalten.
quelle