50 votes

Strange std :: comportement de la carte

Le programme de test suivant

 #include <map>
#include <iostream>

using namespace std;

int main(int argc, char **argv)
{
    map<int,int> a;
    a[1]=a.size();
    for(map<int,int>::const_iterator it=a.begin(); it!=a.end(); ++it)
            cout << "first " << (*it).first << " second " << (*it).second << endl;
}
 

conduit à une sortie différente lorsque compilé sur g++ 4.8.1 (Ubuntu 12.04 LTS):

 g++ xxx.cpp 
./a.out 
first 1 second 1
 

et sur Visual Studio 2012 (Windows 7) (Projet d’application de console Win32 standard):

 ConsoleApplication1.exe
first 1 second 0
 

Quel compilateur a raison? Est-ce que je fais quelque chose de mal?

77voto

Joseph Mansfield Points 59346

C'est en fait un bien formée programme qui a deux tout aussi valables les chemins d'exécution, de sorte que les deux compilateurs sont à droite.

a[1] = a.size()

Dans cette expression, l'évaluation des deux opérandes de l' = sont séquencé.

§1.9/15 [intro.exécution] Sauf indication contraire, l'évaluation des opérandes des opérateurs individuels et des sous-expressions des expressions individuelles sont séquencé.

Toutefois, les appels de fonction ne sont pas entrelacés, de sorte que les appels à l' operator[] et size sont en fait pour une période indéterminée séquencé, plutôt que de séquencé.

§1.9/15 [intro.exécution] Chaque évaluation dans la fonction appelante (y compris d'autres appels de fonction) qui n'est pas spécifiquement séquencée avant ou après l'exécution du corps de la fonction appelée est pour une période indéterminée séquencé à l'égard de l'exécution de la fonction appelée.

Cela signifie que les appels de fonction peut se produire dans l'une des deux commandes:

  1. operator[] alors size
  2. size alors operator[]

Si une clé n'existe pas et que vous appelez operator[] avec cette clé, il sera ajouté à la carte, ce qui modifie la taille de la carte. Ainsi, dans le premier cas, la clé sera ajoutée, la taille sera récupéré (qui est 1), et 1 sera affecté à cette touche. Dans le second cas, la taille sera récupéré (qui est 0), la clé sera ajouté, 0 sera affecté à cette touche.

Remarque, ce n'est pas une situation qui amène à un comportement indéterminé. Comportement indéfini se produit lorsque deux modifications ou à une modification et une lecture de la même scalaire objet sont séquencé.

§1.9/15 [intro.exécution] Si un effet indésirable sur un scalaire objet est séquencé par rapport à un autre effet secondaire sur le même scalaire objet ou d'une valeur de calcul à l'aide de la valeur de la même scalaire objet, le comportement est indéfini.

Dans cette situation, ils ne sont pas non mais pour une période indéterminée séquencé.

Donc, ce que nous avons, c'est deux tout aussi valables les achats de l'exécution du programme. Soit qui pourrait arriver et les deux donnent valide de sortie. C'est indéterminée comportement.

§1.3.25 [defns.non spécifié]
un comportement non spécifié
comportement, pour un bien formée programme de construction et de rectification des données, qui dépend de la mise en œuvre


Donc, pour répondre à vos questions:

Le compilateur est de droite?

Deux d'entre eux sont.

Suis-je en train de faire quelque chose de mal?

Probablement. Il est peu probable que vous voulez écrire du code qui a deux chemins d'exécution de ce genre. Non spécifié comportement peut être bien, à la différence de comportement indéfini, car il peut être résolu en une seule observable de sortie, mais ce n'est pas la peine d'avoir, en premier lieu, si vous pouvez l'éviter. Au lieu de cela, ne pas écrire du code qui a ce genre d'ambiguïté. Selon ce que vous souhaitez exactement chemin d'accès correct, vous pouvez effectuer une des opérations suivantes:

auto size = a.size();
a[1] = size; // value is 0

Ou:

a[1];
a[1] = a.size(); // value is 1

Si vous voulez une 1 et vous savez que la clé n'existe pas encore, vous pouvez bien sûr faire le premier code, mais attribuer size + 1.

15voto

Jefffrey Points 31698

Dans ce cas, en a[1] renvoie un type primitif, veuillez vous référer à cette réponse. Dans le cas où l' std::map's valeur type est un type défini par l'utilisateur et de l' operator=(T, std::size_t) est définie pour ce type, l'expression:

a[1] = a.size();

peut être converti à la correspondante de la moins-syntaxique-sucre version:

a[1] = a.size();
a.operator[](1) = a.size();
operator=(a.operator[](1), a.size());

Et, comme nous le savons tous à partir du §8.3.6/9:

L'ordre d'évaluation des arguments d'une fonction est indéterminée.

ce qui conduit au fait que le résultat de l'expression ci-dessus est non spécifié.

Nous avons, bien sûr, deux cas de figure:

  • Si l' a.operator[](1) est évalué d'abord, la taille de la carte est incrémenté de 1 et menant à la première sortie (first 1 second 1).
  • Si l' a.size() est évalué en premier, le résultat que vous obtenez est la seconde (first 1 second 0).

14voto

CashCow Points 18388

Ceci est connu comme une séquence de points d'émission qui signifie que certaines opérations peuvent être effectuées dans n'importe quel ordre choisi par le compilateur.

Si l'on a des effets secondaires sur les autres, il est appelé "non spécifiée comportement" un peu comme "un comportement indéterminé" toutefois, lorsque le résultat doit être celui d'un fixe sous-ensemble de résultats, donc, ici, il doit être 0 ou 1 et ne peut pas être n'importe quelle valeur. En réalité, vous devriez éviter de le faire.

Dans votre cas particulier. l'exécution operator [] , sur une carte, de ses changements de taille (si cet élément n'existe pas encore). Ainsi, il a un effet secondaire sur le côté droit de ce qu'il est en lui attribuant.

-1voto

ig-melnyk Points 57

Pourquoi ne pas utiliser a.insert(pair<int,int>(1,a.size())); ou a.insert(make_pair(1,a.size()));

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X