2016-05-17 12 views
3

den Code unten geben (sagen, es deque.cpp benannt ist)Singular Iterator Fehler Got mit Iterator in Looping und pop_back

#include <cstdio> 
#include <deque> 

int main() 
{ 
    std::deque<int> d = {1, 2, 3}; 
    for (auto it = d.rbegin(); it != d.rend();) { 
    printf("it: %d\n", *it); 
    ++it; 
    d.pop_back(); 
    } 
    return 0; 
} 

Compile mit g++ -std=c++11 -o deque deque.cpp, läuft es gut:

$ ./deque 
it: 3 
it: 2 
it: 1 

Aber wenn kompilieren mit -D_GLIBCXX_DEBUG (g++ -std=c++11 -o deque_debug deque.cpp -D_GLIBCXX_DEBUG, es wird unter Fehler:

$ ./deque_debug 
it: 3 
/usr/include/c++/4.8/debug/safe_iterator.h:171:error: attempt to copy- 
    construct an iterator from a singular iterator. 
... 

Es sieht so aus, als ob die zweite Schleife ++it aus einem singulären Iterator konstruiert wird. Aber ich denke, nach der ++it der ersten Schleife, zeigt der Iterator auf 2, und pop_back() sollte es nicht ungültig machen. Warum tritt der Fehler dann auf?

Hinweis: Ich weiß, dass der Code wie unten neu schreiben könnte:

while (!d.empty()) { 
    auto it = d.rbegin(); 
    printf("it: %d\n", *it); 
    d.pop_back(); 
    } 

Und der Fehler verschwunden sein.

Aber ich möchte wissen, was genau passiert auf den Fehlercode. (Bedeutet es, die umgekehrte Iterator Dosis nicht tatsächlich mit dem Knotenpunkt erwarte ich, aber das man danach?)


Update: @ Barry Antwort beschlossen, die Frage. Bitte lassen Sie mir eine zusätzliche relevante Frage gestellt: den Code

for (auto it = d.rbegin(); it != d.rend();) { 
    printf("it: %d\n", *it); 
    d.pop_back(); 
    ++it; // <== moved below pop_back() 
    } 

erwartet wird, falsch zu sein, wo ++it auf einem für ungültig erklärt Iterator in Betrieb sein sollte. Aber warum verursacht der Code keinen Fehler?

+1

* Bedeutet es, die umgekehrte Iterator Dosis nicht wirklich zeigen auf den Knoten erwarte ich, aber der eine nach ihm * Ja? ! –

Antwort

4

Das Problem ergibt sich daraus, was eigentlich umgekehrte Iteratoren sind. Die entsprechende Beziehung für eine reverse iterator ist:

For a reverse iterator r constructed from an iterator i , the relationship &*r == &*(i-1) is always true (as long as r is dereferenceable); thus a reverse iterator constructed from a one-past-the-end iterator dereferences to the last element in a sequence.

Wenn wir dann std::deque::pop_back() tun, wir entkräften:

Iterators and references to the erased element are invalidated. The past-the-end iterator is also invalidated. Other references and iterators are not affected.

rbegin() aus end() aufgebaut. Nachdem wir das erste Mal it inkrementiert haben, wird it auf 2 dereferenzieren, aber sein zugrunde liegender Basisiterator zeigt auf 3 - das ist das gelöschte Element. Die Iteratoren, die darauf verweisen, enthalten Ihren nun erweiterten Rückwärts-Iterator. Deshalb ist es ungültig und deshalb sehen Sie den Fehler, den Sie sehen.

Reverse-Iteratoren sind kompliziert.


Statt it inkrementieren, können Sie es zu rbegin() neu zuweisen:

for (auto it = d.rbegin(); it != d.rend();) { 
    printf("it: %d\n", *it); 
    d.pop_back(); 
    // 'it' and 'it+1' are both invalidated 
    it = d.rbegin(); 
} 
+0

Ich sehe keinen Grund, warum ein Iterator für das gelöschte Element nicht immer noch als ein Iterator für eine einzige Nachfolge gültig sein könnte. Ist die Bibliothek oder der Standard hier nicht zu konservativ? – filipos

+0

@filipos "Ich sehe keinen Grund" ist im Allgemeinen kein stichhaltiges Argument. – Barry

+0

Es ist sicherlich kein Argument gegen Ihre Antwort. Aber aus Sicht der Bibliotheksdesigns möchten Sie Iteratoren nicht unnötig invalidieren, um die Nutzung nicht einzuschränken. Es ist daher eine natürliche Frage. Wenn Sie zum Beispiel einen Vektor betrachten (auf den die gleiche Situation zutrifft), bei dem Iteratoren als Zeiger implementiert sind, wird der Zeiger auf das letzte Element nach dem Entfernen ein gültiger Zeiger nach dem Ende. – filipos

0

Durch Löschen des zugrunde liegenden Containers wird ein Iterator ungültig. Zitiert von den Regeln:

Iterators are not dereferenceable if

  • they are past-the-end iterators (including pointers past the end of an array) or before-begin iterators. Such iterators may be dereferenceable in a particular implementation, but the library never assumes that they are.
  • they are singular iterators, that is, iterators that are not associated with any sequence. A null pointer, as well as a default-constructed pointer (holding an indeterminate value) is singular
  • they were invalidated by one of the iterator-invalidating operations on the sequence to which they refer.

Ihr Code bewirkt, dass der Iterator durch die pop_back Operation ungültig gemacht werden, und somit nach dem dritten Punkt oben, wird es nicht dereferenceable.

In Ihrer while Schleife vermeiden Sie dieses Problem, indem Sie bei jeder Schleifenwiederholung einen (neuen) gültigen Iterator erhalten.

+0

Speziell das Problem ist, er erhöht den Iterator * vor * 'pop_back()' statt * nach *. –

+0

Welcher Iterator wird angeblich ungültig gemacht? '++ es' passiert bevor 'pop_back()' – Barry

+0

Dann warum 'für (auto it = d.rbegin(); it! = d.Rend(); ++ es)' den Fehler auch vermeiden? – Mine