Die Zuweisung in C ++ erfolgt trotz Ausnahme auf der rechten Seite

73

Ich habe einen (C ++ 14) Code, der so aussieht:

map<int, set<string>> junk;
for (int id : GenerateIds()) {
    try {
        set<string> stuff = GetStuff();
        junk[id] = stuff;
    } catch (const StuffException& e) {
        ...
    }
}

Das funktioniert. Löst manchmal GetStuff()eine Ausnahme aus, die gut funktioniert, da ich dann keinen Wert in der Junk-Map haben möchte.

Aber zuerst hatte ich das in die Schleife geschrieben, was nicht funktioniert:

junk[id] = GetStuff();

Genauer gesagt, selbst wenn GetStuff()eine Ausnahme ausgelöst wird, junk[id]wird sie erstellt (und eine leere Menge zugewiesen).

Das würde ich nicht erwarten: Ich würde erwarten, dass sie genauso funktionieren.

Gibt es ein Prinzip von C ++, das ich hier missverstanden habe?

jma
quelle
14
Nur um für zukünftige Leser mit ähnlichen Problemen zu klären: Hier findet keine Zuordnung statt . junk[id]erstellt ein neues set, ja, aber das verwendet den Standardkonstruktor. Deshalb ist das Set leer. Diese leere Menge wäre als Objekt zum Zuweisen verwendet worden, wenn GetStuff()dies erfolgreich gewesen wäre. Die Ausnahme ist jedoch genau, warum keine Zuordnung erfolgt. Das Set bleibt im Standardzustand. Es ist ein richtiges C ++ - Objekt, und Sie können seine Mitglieder normal aufrufen. Dh junk[id].size()wird danach 0 sein.
MSalters
1
@ MSalters Gute Klarstellung! Ich habe "Zuweisung" zu locker verwendet (aber "Standard konstruiert" war vielleicht etwas schwer für den Fragentitel ;-).
JMA
2
Um dies noch sicherer (und effizienter) zu machen, sollte die Zuordnung sein junk[id] = std::move(stuff);.
David Hammen

Antworten:

96

Vor C ++ 17 gab es keine Sequenzierung zwischen der linken und rechten Seite der Zuweisungsoperatoren.

In C ++ 17 wurde erstmals eine explizite Sequenzierung eingeführt (die rechte Seite wird zuerst ausgewertet).

Dies bedeutet, dass die Bewertungsreihenfolge nicht angegeben ist. Dies bedeutet, dass die Implementierung die Bewertung in der gewünschten Reihenfolge durchführen muss. In diesem Fall wird zuerst die linke Seite bewertet.

Weitere Einzelheiten finden Sie in dieser Referenz zur Bewertungsreihenfolge (insbesondere unter Punkt 20).

Ein Programmierer
quelle
4
Wenn ich die Referenz richtig verstehe, könnte der erste Satz in der Antwort durch Entfernen von "überladen" verkürzt werden, da die rechte Seite zuerst für überladene und nicht überladene Zuweisungsoperatoren in C ++ 17 ausgewertet wird.
Hans Olsson
17

std :: map :: operator []

Gibt einen Verweis auf den Wert zurück, der einem Schlüssel zugeordnet ist, der dem Schlüssel entspricht, und führt eine Einfügung durch, falls dieser Schlüssel noch nicht vorhanden ist.

junk[id]bewirkt das oben erwähnte Einfügen und danach sind schon GetStuff()Würfe passiert . Beachten Sie, dass in C ++ 14 die Reihenfolge, in der diese Dinge geschehen, durch die Implementierung definiert ist. Mit einem anderen Compiler junk[id] = GetStuff();können Sie das Einfügen möglicherweise nicht durchführen, wenn GetStuff()Würfe ausgeführt werden.

Ted Lyngmo
quelle
12

Sie verstehen falsch, wie es operator[]funktioniert std::map.

Es gibt einen Verweis auf das zugeordnete Element zurück. Daher fügt Ihr Code zuerst ein Standardelement an dieser Position ein und ruft dann auf operator=, um einen neuen Wert festzulegen.

Damit dies wie erwartet funktioniert, müssen Sie std::map::insert(*) verwenden:

junk.insert(std::make_pair(id, GetStuff()));

Vorsichtsmaßnahme : insertFügt den Wert nur hinzu, wenn er idnoch nicht zugeordnet ist.

Paddy
quelle
6
Ich glaube nicht, dass es ein Missverständnis darüber gibt, wie es hier operator[]funktioniert. Auch aus Ihrer Antwort ist nicht klar, warum operator=die linke Seite bewertet, auch wenn die rechte Seite eine Ausnahme
auslöst
Wenn es ein Missverständnis gibt, wäre es für mich ein Missverständnis für a [b] = ...; nicht nur für einen [...].
Hans Olsson
8
@ user463035818: Der Fragesteller äußert die Überzeugung, dass eine Zuweisung stattgefunden hat, was stark darauf hindeutet, dass er nicht weiß, dass durch die Auswertung des operator[]selbst ein leerer Satz erstellt werden würde junk[id].
user2357112 unterstützt Monica
1
Upvoted für den Vorschlag einer Möglichkeit, es zu schreiben, ohne zuerst einen Eintrag standardmäßig zu erstellen, unabhängig davon, ob ein "Missverständnis" vorliegt oder nicht.
Peter Cordes