Basierend auf dem, was in C ++ 11 als idiomatisch angesehen wird:
- Sollte ein Iterator in einem benutzerdefinierten Container den zerstörten Container selbst überleben?
- sollte es möglich sein zu erkennen, wann ein Iterator ungültig wird?
- Sind die oben genannten Bedingungen in der Praxis von "Debug-Builds" abhängig?
Details : Ich habe kürzlich mein C ++ aufgefrischt und mich in C ++ 11 zurechtgefunden. Als Teil davon habe ich einen idiomatischen Wrapper um die Uriparser-Bibliothek geschrieben . Ein Teil davon ist das Umschließen der verknüpften Listendarstellung von analysierten Pfadkomponenten. Ich suche Rat, was für Container idiomatisch ist.
Eine Sache, die mich beunruhigt, da ich zuletzt aus durch Müll gesammelten Sprachen stamme, ist sicherzustellen, dass zufällige Objekte nicht nur bei Benutzern verschwinden, wenn sie einen Fehler in Bezug auf die Lebensdauer machen. Um dies zu berücksichtigen, PathList
behalten sowohl der Container als auch seine Iteratoren ein shared_ptr
Objekt für den tatsächlichen internen Status bei. Dies stellt sicher, dass, solange etwas vorhanden ist, das auf diese Daten hinweist, auch die Daten vorhanden sind.
Wenn man sich jedoch die STL (und viele Suchanfragen) ansieht , sieht es nicht so aus, als würden C ++ - Container dies garantieren. Ich habe den schrecklichen Verdacht, dass die Erwartung besteht, Container einfach zerstören zu lassen und damit alle Iteratoren ungültig zu machen. std::vector
scheint sicherlich zuzulassen, dass Iteratoren ungültig werden und trotzdem (falsch) funktionieren.
Was ich wissen möchte ist: Was wird von "gutem" / idiomatischem C ++ 11-Code erwartet? Angesichts der glänzenden neuen intelligenten Zeiger erscheint es seltsam, dass Sie mit STL Ihre Beine leicht abblasen können, indem Sie versehentlich einen Iterator auslaufen lassen. Ist die Verwendung shared_ptr
der Sicherungsdaten eine unnötige Ineffizienz, eine gute Idee zum Debuggen oder etwas, das von STL einfach nicht erwartet wird?
(Ich hoffe, dass durch die Erdung auf "idiomatisches C ++ 11" Anklagen wegen Subjektivität vermieden werden ...)
Wenn Sie in C ++ den Container zerstören lassen, werden die Iteratoren ungültig. Zumindest bedeutet dies, dass der Iterator nutzlos ist, und wenn Sie versuchen, ihn zu dereferenzieren, können viele schlechte Dinge passieren (genau wie schlecht hängt von der Implementierung ab, aber es ist normalerweise ziemlich schlecht).
In einer Sprache wie C ++ liegt es in der Verantwortung des Programmierers, solche Dinge klar zu halten. Das ist eine der Stärken der Sprache, denn Sie können sich ziemlich darauf verlassen, wann etwas passiert (Sie haben ein Objekt gelöscht? Das bedeutet, dass zum Zeitpunkt des Löschens der Destruktor aufgerufen und der Speicher freigegeben wird und Sie sich darauf verlassen können dazu), aber es bedeutet auch, dass Sie Iteratoren nicht überall in Containern aufbewahren und diesen Container dann löschen können.
Könnten Sie jetzt einen Container schreiben, der die Daten so lange aufbewahrt, bis alle Iteratoren verschwunden sind? Natürlich haben Sie das klar im Griff. Das ist NICHT die übliche C ++ - Methode, aber es ist nichts Falsches daran, solange es ordnungsgemäß dokumentiert (und natürlich debuggt) ist. So funktionieren die STL-Container einfach nicht.
quelle
Einer der (oft nicht genannten) Unterschiede zwischen C ++ - und GC-Sprachen besteht darin, dass die gängige C ++ - Sprache davon ausgeht, dass alle Klassen Wertklassen sind.
Es gibt Zeiger und Referenzen, aber sie sind meistens verbannt, wenn es darum geht, polymorphen Versand (über die Indirektion virtueller Funktionen) zuzulassen oder Objekte zu verwalten, deren Lebensdauer die des Blocks überleben muss, der sie erstellt hat.
In diesem letzten Fall liegt es in der Verantwortung des Programmierers, die Politik und Politik darüber zu definieren, wer schafft und wer und wann zerstören muss. Intelligente Zeiger (wie
shared_ptr
oderunique_ptr
) sind nur Werkzeuge, die bei dieser Aufgabe in ganz bestimmten (und häufigen) Fällen helfen, in denen ein Objekt von verschiedenen Eigentümern "geteilt" wird (und Sie möchten, dass das letzte Objekt es zerstört) oder kontextübergreifend verschoben werden müssen immer einen einzigen Kontext haben, der es besitzt.Interatoren sind von Natur aus nur während ... einer Iteration sinnvoll, und daher sollten sie nicht "zur späteren Verwendung gespeichert" werden, da das, worauf sie sich beziehen, nicht gewährt wird, um gleich zu bleiben oder dort zu bleiben (ein Container kann es verschieben Inhalt beim Wachsen oder Schrumpfen ... alles ungültig machen). Linkbasierte Container (wie
list
s) sind eine Ausnahme von dieser allgemeinen Regel, nicht die Regel selbst.Wenn in der idiomatischen C ++ A B "braucht", muss B an einem Ort sein, der länger lebt als der Ort, an dem A ist, daher ist keine "Lebensverfolgung" von B von A erforderlich.
shared_ptr
undweak_ptr
helfen Sie, wenn diese Redewendung zu restriktiv ist, indem Sie jeweils die Richtlinien "Gehen Sie nicht weg, bis wir alle es zulassen" oder die Richtlinien "Wenn Sie weggehen, hinterlassen Sie uns einfach eine Nachricht" zulassen. Aber sie haben Kosten, da sie dazu einige Hilfsdaten zuweisen müssen.Der nächste Schritt sind gc_ptr-s (die die Standardbibliothek nicht bietet, die Sie jedoch implementieren können, wenn Sie möchten, beispielsweise mit Mark & Sweep-Algorithmen), bei denen die Tracking-Strukturen noch komplexer und prozessorintensiver sind ihre Wartung.
quelle
In C ++ ist es idiomatisch, irgendetwas davon zu machen
ein undefiniertes Verhalten .
Insbesondere bei Iteratoren wird in der Dokumentation der einzelnen Container angegeben, welche Operationen Iteratoren ungültig machen (die Zerstörung des Containers gehört immer dazu), und der Zugriff auf ungültige Iteratoren ist undefiniertes Verhalten. In der Praxis bedeutet dies, dass die Laufzeit blind auf den nicht mehr gültigen Zeiger zugreift. Normalerweise stürzt es ab, aber es kann den Speicher beschädigen und zu völlig unvorhersehbaren Ergebnissen führen.
Es wird empfohlen, optionale Überprüfungen bereitzustellen, die im Debug-Modus aktiviert werden können (
#define
standardmäßig aktiviert, wenn_DEBUG
definiert und deaktiviert, wenn dies der FallNDEBUG
ist).Denken Sie jedoch daran, dass C ++ für Fälle ausgelegt ist, in denen jede Leistung benötigt wird und die Überprüfungen manchmal recht kostspielig sein können, da Iteratoren häufig in engen Schleifen verwendet werden. Aktivieren Sie sie daher nicht standardmäßig.
In unserem Arbeitsprojekt musste ich die Iteratorprüfung in der Microsoft-Standardbibliothek auch im Debug-Modus deaktivieren, da einige Container andere Container und Iteratoren intern verwenden und die Zerstörung eines großen Containers aufgrund der Prüfungen eine halbe Stunde dauerte!
quelle