Ist es in C ++ in Ordnung, Ressourcen von einer Karte zu stehlen, die ich danach nicht mehr benötige? Genauer gesagt, ich habe einen std::map
mit std::string
Schlüsseln und möchte einen Vektor daraus konstruieren, indem ich die Ressourcen der map
s-Schlüssel mit stehle std::move
. Beachten Sie, dass ein solcher Schreibzugriff auf die Schlüssel die interne Datenstruktur (Reihenfolge der Schlüssel) des Schlüssels beschädigt, map
aber ich werde sie danach nicht mehr verwenden.
Frage : Kann ich dies ohne Probleme tun oder führt dies zu unerwarteten Fehlern, beispielsweise im Destruktor von, map
weil ich auf eine Weise darauf zugegriffen habe, std::map
die nicht dafür vorgesehen war?
Hier ist ein Beispielprogramm:
#include<map>
#include<string>
#include<vector>
#include<iostream>
using namespace std;
int main(int argc, char *argv[])
{
std::vector<std::pair<std::string,double>> v;
{ // new scope to make clear that m is not needed
// after the resources were stolen
std::map<std::string,double> m;
m["aLongString"]=1.0;
m["anotherLongString"]=2.0;
//
// now steal resources
for (auto &p : m) {
// according to my IDE, p has type
// std::pair<const class std::__cxx11::basic_string<char>, double>&
cout<<"key before stealing: "<<p.first<<endl;
v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));
cout<<"key after stealing: "<<p.first<<endl;
}
}
// now use v
return 0;
}
Es erzeugt die Ausgabe:
key before stealing: aLongString
key after stealing:
key before stealing: anotherLongString
key after stealing:
BEARBEITEN: Ich möchte dies für den gesamten Inhalt einer großen Karte tun und dynamische Zuordnungen durch das Stehlen von Ressourcen speichern.
quelle
const
Werts ist immer UB.std::string
hat Short-String-Optimierung. Das bedeutet, dass es eine nicht triviale Logik beim Kopieren und Verschieben gibt und nicht nur beim Zeigertausch. Außerdem bedeutet das Verschieben die meiste Zeit das Kopieren - damit Sie sich nicht mit ziemlich langen Zeichenfolgen befassen. Der statistische Unterschied war sowieso gering und variiert im Allgemeinen sicherlich abhängig davon, welche Art von Zeichenfolgenverarbeitung durchgeführt wird.Antworten:
Sie führen ein undefiniertes Verhalten aus, mit
const_cast
dem Sie eineconst
Variable ändern . Tu das nicht. Der Grund dafürconst
ist, dass Karten nach ihren Schlüsseln sortiert sind. Das Ändern eines Schlüssels an Ort und Stelle bricht also die zugrunde liegende Annahme, auf der die Karte basiert.Sie sollten niemals verwenden
const_cast
, umconst
aus einer Variablen zu entfernen und diese Variable zu ändern.Davon abgesehen hat C ++ 17 die Lösung für Ihr Problem:
std::map
'sextract
Funktion:Und nicht
using namespace std;
. :) :)quelle
map
wird sich nicht beschweren, wenn ich seine Methoden nicht aufrufe (was ich nicht tue) und vielleicht ist die interne Reihenfolge in seinem Destruktor nicht wichtig?const
. Das Mutieren einesconst
Objekts erfolgt sofort nach UB, unabhängig davon, ob danach tatsächlich etwas darauf zugreift oder nicht.extract
wenn Iteratoren als Argument verwendet werden, hat sich die konstante Komplexität amortisiert. Ein gewisser Overhead ist unvermeidlich, aber wahrscheinlich nicht signifikant genug, um eine Rolle zu spielen. Wenn Sie spezielle Anforderungen haben, die hiervon nicht abgedeckt sind, müssen Sie Ihre eigenen implementierenmap
, um diese Anforderungen zu erfüllen. Diestd
Behälter sind für den allgemeinen Gebrauch gedacht. Sie sind nicht für bestimmte Anwendungsfälle optimiert.const
? In diesem Fall können Sie bitte angeben, wo meine Argumentation falsch ist.Ihr Code versucht,
const
Objekte zu ändern , sodass er ein undefiniertes Verhalten aufweist, wie die Antwort von druckermanly richtig hervorhebt .Einige andere Antworten ( Phinz und Deuchie ) argumentieren, dass der Schlüssel nicht als
const
Objekt gespeichert werden darf, da das Knotenhandle , das sich aus dem Extrahieren von Knoten aus der Karte ergibt, den Nichtzugriffconst
auf den Schlüssel ermöglicht. Diese Schlussfolgerung mag zunächst plausibel erscheinen, aber P0083R3 , das Papier, in dem dieextract
Funktionen eingeführt wurden , enthält einen eigenen Abschnitt zu diesem Thema, der dieses Argument ungültig macht:(Hervorhebung von mir)
quelle
Ich denke nicht, dass
const_cast
Änderungen in diesem Fall zu undefiniertem Verhalten führen, aber bitte kommentieren Sie, ob diese Argumentation korrekt ist.Diese Antwort behauptet das
Die Zeile
v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));
in der Frage führt also nicht genau dann zu UB, wenn dasstring
Objektp.first
nicht als const-Objekt erstellt wurde. Beachten Sie nun, dass die Referenz überextract
ZuständeAlso, wenn ich
extract
dasnode_handle
entsprechendep
,p
lebe weiterhin an seinem Lagerort. Aber nach der Extraktion darf ichmove
die Ressourcenp
wie im Code von druckermanlys Antwort wegnehmen . Dies bedeutet , dassp
und damit auch dasstring
Objektp.first
wurde nicht ursprünglich als konstantes Objekt erstellt.Daher denke ich, dass die Änderung der
map
Schlüssel des Benutzers nicht zu UB führt, und aus Deuchies Antwort geht hervor , dass auch die jetzt beschädigte Baumstruktur (jetzt mehrere gleiche leere Zeichenfolgenschlüssel) desmap
nicht zu Problemen im Destruktor führt. Daher sollte der Code in der Frage zumindest in C ++ 17, in dem dieextract
Methode vorhanden ist, einwandfrei funktionieren (und die Aussage über gültige Zeiger gilt).Aktualisieren
Ich bin jetzt der Ansicht, dass diese Antwort falsch ist. Ich lösche es nicht, weil andere Antworten darauf verweisen.
quelle
std::launder
.EDIT: Diese Antwort ist falsch. Freundliche Kommentare haben auf die Fehler hingewiesen, aber ich lösche sie nicht, weil in anderen Antworten darauf verwiesen wurde.
@druckermanly beantwortete Ihre erste Frage, die besagte, dass das
map
gewaltsame Ändern der Schlüssel die Ordnungsmäßigkeit dermap
internen Datenstruktur (Rot-Schwarz-Baum) beeinträchtigt . Es ist jedoch sicher, dieextract
Methode zu verwenden , da sie zwei Dinge bewirkt: Bewegen Sie den Schlüssel aus der Karte und löschen Sie ihn dann, damit die Ordnungsmäßigkeit der Karte überhaupt nicht beeinträchtigt wird.Die andere Frage, die Sie gestellt haben, ob sie beim Dekonstruieren Probleme verursachen würde, ist kein Problem. Wenn eine Karte dekonstruiert, ruft sie den Dekonstruktor jedes ihrer Elemente (mapped_types usw.) auf, und die
move
Methode stellt sicher, dass es sicher ist, eine Klasse nach dem Verschieben zu dekonstruieren. Also mach dir keine Sorgen. Kurz gesagt, es ist die Operationmove
, die sicherstellt, dass es sicher ist, einen neuen Wert zu löschen oder der "verschobenen" Klasse neu zuzuweisen. Speziell fürstring
kann diemove
Methode ihren Zeichenzeiger auf setzennullptr
, damit nicht die tatsächlichen Daten gelöscht werden, die beim Aufruf des Dekonstruktors der ursprünglichen Klasse verschoben wurden.Ein Kommentar erinnerte mich an den Punkt, den ich übersah, im Grunde hatte er Recht, aber eines stimme ich nicht ganz zu: Es
const_cast
ist wahrscheinlich kein UB.const
ist nur ein Versprechen zwischen dem Compiler und uns. Objekte, von denen festgestellt wird , dass sie in Bezug auf ihre Typen und Darstellungen in binärer Formconst
immer noch ein Objekt sind, wie diejenigen, die es noch nichtconst
sind. Wenn dasconst
weggeworfen wird, sollte es sich so verhalten, als wäre es eine normale veränderbare Klasse. In Bezug aufmove
: Wenn Sie es verwenden möchten, müssen Sie ein&
anstelle von a übergebenconst &
. Da es sich also nicht um ein UB handelt, wird das Versprechen einfach gebrochenconst
und die Daten werden verschoben.Ich habe auch zwei Experimente mit MSVC 14.24.28314 und Clang 9.0.0 durchgeführt, und sie ergaben das gleiche Ergebnis.
Ausgabe:
Wir können sehen, dass die Zeichenfolge '2' jetzt leer ist, aber offensichtlich haben wir die Ordnungsmäßigkeit der Karte gebrochen, weil die leere Zeichenfolge vorne platziert werden sollte. Wenn wir versuchen, bestimmte Knoten der Karte einzufügen, zu finden oder zu löschen, kann dies zu einer Katastrophe führen.
Auf jeden Fall können wir uns einig sein, dass es niemals eine gute Idee ist, die inneren Daten von Klassen unter Umgehung ihrer öffentlichen Schnittstellen zu manipulieren. Die
find
,insert
,remove
Funktionen und so verlassen hervor ihre Richtigkeit auf der Ordnung der inneren Datenstruktur, und das ist der Grund , warum wir von dem Gedanken an spähen nach innen bleiben weg sollten.quelle
const
Wertes) früher aufgetreten ist. Das Argument "Diemove
[Funktion] stellt sicher, dass es sicher ist, [ein Objekt] einer Klasse nach dem Verschieben zu dekonstruieren" gilt jedoch nicht: Sie können nicht sicher von einemconst
Objekt / einer Referenz verschieben, da dies eine Änderung erfordertconst
verhindert. Sie können versuchen, diese Einschränkung mithilfe von zu umgehenconst_cast
, aber an diesem Punkt gehen Sie bestenfalls auf ein tiefgreifendes implementierungsspezifisches Verhalten ein, wenn nicht auf UB.move
Funktion a&
anstelle von aconst&
nimmt. Wenn man also darauf besteht, dass er einen Schlüssel aus einer Karte herauszieht, muss er verwendenconst_cast
.const_cast
wird also widerlich sein. Es mag die meiste Zeit funktionieren, aber brechen Sie Ihren Code auf subtile Weise.extract
wir uns danach von demselben Objekt bewegen dürfen, oder? (siehe meine Antwort)std::launder