2015-02-26 15 views
6
#include <string> 
#include <iostream> 

int main() { 
    std::string s = "abcdef"; 

    std::string s2 = s; 

    auto begin = const_cast<std::string const &>(s2).begin(); 
    auto end = s2.end(); 

    std::cout << end - begin << '\n'; 
} 

Dieser Code mischt das Ergebnis von begin() const mit dem Ergebnis end(). Keine dieser Funktionen darf Iteratoren ungültig machen. Allerdings bin ich gespannt, ob die Anforderung end(), die Iterator-Variable begin nicht ungültig zu machen, tatsächlich bedeutet, dass die Variable begin mit end verwendbar ist.Iterator-Invalidierung durch `std :: string :: begin()`/`std :: string :: end()`?

Betrachten Sie eine C++ 98, Copy-on-Write-Implementierung von std::string; Die nicht-const begin() und end() Funktionen verursachen, einen internen Puffer zu kopieren, da das Ergebnis dieser Funktionen zu dem Ändern der Zeichenfolge verwandt werden kann. So beginnt begin oben für beide s und s2, aber die Verwendung der nicht-const end() Mitglied verursacht es nicht mehr gültig für s2, der Container, der es produziert.

Der obige Code erzeugt "unerwartete" Ergebnisse mit einer Copy-on-Write-Implementierung, z. B. libstdC++. Anstelle von end - begin ist das gleiche wie s2.size(), libstdC++ produces another number.

  • Hat verursacht begin nicht mehr gültig Iterator zu sein in s2, wobei der Behälter aus abgerufen wurde, darstellen ‚Ungültigkeits‘ den Iterator? Wenn Sie sich die Anforderungen für Iteratoren ansehen, scheinen alle für diesen Iterator zu warten, nachdem .end() aufgerufen wurde, so dass sich begin immer noch als gültiger Iterator qualifiziert und somit nicht ungültig gemacht wurde?

  • Ist der obige Code in C++ 98 gut definiert? In C++ 11, die Copy-on-Write-Implementierungen verbietet?

Aus meiner eigenen kurzen Lesung der Spezifikationen, scheint es unter festgelegtem, so dass es keine Garantie dafür, dass die Ergebnisse der begin() und end() immer zusammen verwendet werden können, auch ohne const und nicht-Misch konstante Versionen

+1

Der Grund, warum C++ 11 COW ausdrücklich verboten hat, ist genau dieses Problem: Ihr Code ist konform und sollte "6" ergeben, aber offensichtlich nicht. Die COW-Implementierung ist nicht konform. –

+0

libC++ bekommt das richtig. [Live] (http://coliru.stacked-crooked.com/a/e0d48d1f709b1eb8). –

+0

@BaummitAugen Für eine Definition von "richtig". Der Code in der Frage ist nicht vor C++ 11 legal und funktioniert nicht (oder ist nicht garantiert) mit Bibliotheken vor C++ 11 (einschließlich der Standardbibliothek, die mit g ++ geliefert wird). Die Bibliothek ist nicht falsch, wenn sie fehlschlägt; der Code ist. –

Antwort

6

Wie Sie sagen, C++ 11 unterscheidet sich in dieser Hinsicht von früheren Versionen. In C++ 11 gibt es kein Problem, da alle Versuche, Kopieren beim Schreiben zu erlauben, entfernt wurden. In pre-C++ 11 führt Ihr Code zu undefiniertem Verhalten; Der Aufruf s2.end() darf vorhandene Iteratoren ungültig machen (und tat dies in g ++).

Beachten Sie, dass der Standard, selbst wenn s2 keine Kopie wäre, Iteratoren ungültig machen könnte. Tatsächlich hat die CD für C++ 98 sogar Dinge wie f(s.begin(), s.end()) oder s[i] == s[j] undefiniertes Verhalten gemacht. Dies wurde erst in letzter Minute realisiert und korrigiert, so dass nur der erste Aufruf an begin(), end() oder [] die Iteratoren ungültig machen konnte.

+0

"Verweise, Zeiger und Iteratoren, die sich auf die Elemente einer basic_string-Sequenz beziehen, können durch die folgenden Verwendungen dieses basic_string-Objekts ungültig gemacht werden: Aufruf von nicht-konstanten Elementfunktionen, außer operator [](), at(), begin(), rbegin(), end() und rend(). " Ab C++ 03. –

+0

@LightnessRacesinOrbit Und der unmittelbar folgende Punkt? –

+0

Ah. –

2

Der Code ist OK: Eine CoW-Implementierung ist ziemlich erforderlich, um die Freigabe aufzuheben, wenn die Gefahr besteht, dass ein Iterator oder ein Verweis auf ein Element gehalten wird. Das heißt, wenn Sie etwas haben, das auf ein Element in einer Zeichenkette zugreift und eine Kopie davon das gleiche tut, d. H. Einen Iterator oder den Subscript-Operator verwendet, muss es freigegeben werden. Es könnte über seine Iteratoren wissen und sie bei Bedarf aktualisieren.

Natürlich ist es in einem gleichzeitigen System fast unmöglich, all dies ohne Datenrennen zu tun, aber vor C++ 11 gibt es keine Datenrennen.

+0

Der Code ist nicht korrekt, da das Aufheben der Freigabe im Aufruf von 's2.end() 'den durch den vorherigen Aufruf von' s1.begin()' zurückgegebenen Iterator ungültig macht (durch eine const-Referenz). (Und natürlich: Sie haben im letzten Satz ein oder zwei Wörter vergessen. Verwenden Sie einen Mutex korrekt, und es ist einfach, Datenrennen zu vermeiden. Was Sie zweifellos meinen, ist "fast unmöglich, dies ohne Datenrennen zu tun" und "mit akzeptabler Leistung" "..) –

+0

@JamesKanze: Wenn die Freigabe bei der Verwendung von' s2.end() 'Dinge ungültig machen würde, müsste das Entteilen beim Aufruf von 's2.begin()' erfolgen. –

+0

Nicht wie ich es verstehe. Sein Aufruf an 'begin()' erfolgt durch einen 'const' lvalue, also ruft er' begin() const' auf. Durch das Aufheben der Freigabe werden Iteratoren ungültig gemacht, und eine Implementierung darf Iteratoren nicht ungültig machen, wenn 'begin() const 'aufgerufen wird. –

2

Ab N3337 (which is essentially identical to C++11), liest die Spezifikation ([string.require]/4):

Referenzen, Zeiger und Iteratoren zu den Elementen einer Folge basic_string Bezug kann durch die folgende ungültig gemacht werden, Verwendungen dieses basic_string-Objekts:
[...]
- Aufruf von nicht-const Elementfunktionen, außer operator [], at, front, back, begin, rbegin, end und rend.

Mindestens wie ich es gelesen habe, bedeutet dies, dass ein Aufruf an begin oder end ist keine Iteratoren ungültig zu machen erlaubt. Obwohl nicht direkt angegeben, würde ich dies auch so verstehen, dass kein Aufruf einer const Mitgliedsfunktion Iteratoren ungültig machen kann.

Diese Formulierung bleibt zumindest bis n4296 gleich.

+0

n4296 postdates C++ 14, so beantwortet dies nicht die Frage nach C++ 98 und C++ 11. Die Schlussfolgerung ist jedoch in diesen Normen aufgrund der gleichen (oder ähnlichen) Formulierung dieselbe. –

+0

Derselbe Text existiert in C++ 98 und C++ 11, aber wenn Sie sich die Anforderungen für Iteratoren ansehen, scheinen alle die Variable 'begin' nach dem Aufruf von' end() 'zu enthalten. Daher scheint es so zu sein, dass "begin" überhaupt nicht für ungültig erklärt wurde, obwohl es mit dem Ergebnis von "end()" unbrauchbar ist. Es scheint nur, dass es keine Anforderungen an 'begin()' oder 'end()' gibt, die erfordern, dass ihre Ergebnisse zusammen verwendet werden können. – bames53

+0

Die Anforderungen in C++ vor C++ 11 unterscheiden sich erheblich. Durch die CD2 von C++ 98 kann jeder Aufruf des nicht-konstanten '[]', 'at()', 'begin()' oder 'end()' Iteratoren, Verweise und Zeiger ungültig machen. Zwischen CD2 und C++ 98 versuchte das Komitee dies zu beheben, indem er sagte, dass nur der erste Aufruf Iteratoren ungültig machen könnte. In C++ 11 änderten sie es, um zu sagen, dass kein Anruf konnte, der effektiv Kopie beim Schreiben verbot. In dem dargestellten Code erhält das Programm einen Iterator durch einen Aufruf von 'begin() const' und ruft dann 'end()' auf, wobei pre-C++ 11 den ersten Iter ungültig machen könnte. –

1

C++ 98 [lib.basic.string]/5 lautet:

Referenzen, Zeiger und Iteratoren zu den Elementen einer Folge basic_string Bezug kann durch die folgenden Verwendungen des Objekts ungültig gemacht werden basic_string :

  • als Argument für Drittfunktionen swap(), operator>>() und getline().

  • Als ein Argument zu basic_string::swap().

  • Aufruf von data() und c_str() Mitgliedsfunktionen.

  • Aufruf nicht-const Member-Funktionen, mit Ausnahme operator[](), at(), begin(), rbegin(), end() und rend().

  • Im Anschluss an einen der oben genannten Verwendungen mit Ausnahme der Formen des insert() und erase() Iteratoren, die Rückkehr zum ersten Gespräch nicht konstanten Elementfunktionen operator[](), at(), begin(), rbegin(), end() oder rend().

Da der Konstruktor von s2 ist eine „non-const Member-Funktion“, ist es für den Anruf nicht konstante konformen s2.end() - der erste derartige Aufruf pro letzter Kugel oben - Iteratoren ungültig zu machen. Das Programm hat daher kein definiertes Verhalten nach C++ 98.

Ich werde nicht zu C++ 11 kommentieren, da ich denke, dass die anderen Antworten klar erklären, dass das Programm in diesem Kontext Verhalten definiert hat.

Verwandte Themen