web-dev-qa-db-fra.com

L'affectation en C ++ a lieu malgré une exception du côté droit

J'ai du code (C++ 14) qui ressemble à ceci:

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

Cela marche. Parfois, GetStuff() lève une exception, ce qui fonctionne bien, car sinon, je ne veux pas de valeur dans la carte de courrier indésirable.

Mais au début, j'avais écrit ceci dans la boucle, ce qui ne fonctionnait pas:

junk[id] = GetStuff();

Plus précisément, même lorsque GetStuff() lève une exception, junk[id] est créé (et un ensemble vide lui est attribué).

Ce n'est pas ce à quoi je m'attendais: je m'attendrais à ce qu'ils fonctionnent de la même manière.

Existe-t-il un principe de C++ que j'ai mal compris ici?

72
jma

Avant C++ 17, il n'y avait pas de séquence entre les côtés gauche et droit des opérateurs d'affectation.

C'est la première fois en C++ 17 que le séquençage explicite a été introduit (le côté droit est évalué en premier).

Cela signifie que l'ordre d'évaluation est non spécifié , ce qui signifie qu'il incombe à la mise en oeuvre d'effectuer l'évaluation dans l'ordre dans lequel elle le souhaite, et dans ce cas évalue le côté gauche en premier.

Voir cette référence d’ordre d’évaluation pour plus de détails (en particulier le point 20).

96

std :: map :: operator []

Renvoie une référence à la valeur mappée sur une clé équivalente à une clé, en effectuant une insertion si cette clé n'existe pas déjà.

junk[id] provoque l'insertion mentionnée ci-dessus et après cela est déjà arrivé GetStuff() lance. Notez que dans C++ 14 l'ordre dans lequel ces choses se passent est défini par l'implémentation. Ainsi, avec un compilateur différent, votre junk[id] = GetStuff(); ne peut pas effectuer l'insertion si GetStuff() est émis.

17
Ted Lyngmo

Vous comprenez mal comment operator[] fonctionne sur std::map.

Il renvoie une référence à l'élément mappé. Par conséquent, votre code insère d’abord un élément par défaut à cette position, puis appelle operator= pour définir une nouvelle valeur.

Pour que cela fonctionne comme vous le souhaitez, vous devez utiliser std::map::insert (*):

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

Caveat: insert ajoutera la valeur uniquement si id n'est pas déjà mappé.

12
paddy