web-dev-qa-db-fra.com

Ordre d'exécution C ++ dans le chaînage des méthodes

La sortie de ce programme:

#include <iostream> 
class c1
{   
  public:
    c1& meth1(int* ar) {
      std::cout << "method 1" << std::endl;
      *ar = 1;
      return *this;
    }
    void meth2(int ar)
    {
      std::cout << "method 2:"<< ar << std::endl;
    }
};

int main()
{
  c1 c;
  int nu = 0;
  c.meth1(&nu).meth2(nu);
}

Est:

method 1
method 2:0

Pourquoi nu n'est-il pas 1 lorsque meth2() démarre?

103
Moises Viñas

Parce que l'ordre d'évaluation n'est pas spécifié.

Vous voyez nu dans main en cours d'évaluation en 0 avant même meth1 est appelé. C'est le problème du chaînage. Je conseille de ne pas le faire.

Créez simplement un programme agréable, simple, clair, facile à lire et à comprendre:

int main()
{
  c1 c;
  int nu = 0;
  c.meth1(&nu);
  c.meth2(nu);
}

Je pense que cette partie du projet de norme concernant l'ordre d'évaluation est pertinente:

1.9 Exécution du programme

...

  1. Sauf indication contraire, les évaluations d'opérandes d'opérateurs individuels et de sous-expressions d'expressions individuelles ne sont pas séquencées. Les calculs de valeur des opérandes d'un opérateur sont séquencés avant le calcul de valeur du résultat de l'opérateur. Si un effet secondaire sur un objet scalaire n'est pas séquencé par rapport à un autre effet secondaire sur le même objet scalaire ou à un calcul de valeur utilisant la valeur du même objet scalaire, et qu'ils ne sont pas potentiellement concurrents, le comportement n'est pas défini

et aussi:

5.2.2 Appel de fonction

...

  1. [Remarque: Les évaluations de l'expression postfixe et des arguments ne sont pas séquencées les unes par rapport aux autres. Tous les effets secondaires des évaluations d'arguments sont séquencés avant la fonction est entré - note de fin]

Donc, pour votre ligne c.meth1(&nu).meth2(nu);, considérez ce qui se passe dans l'opérateur en termes d'opérateur d'appel de fonction pour l'appel final à meth2, Donc nous voyons clairement la répartition dans l'expression et l'argument postfix nu:

operator()(c.meth1(&nu).meth2, nu);

Les évaluations de l'expression et de l'argument postfixe pour l'appel de fonction final (c'est-à-dire l'expression postfixe c.meth1(&nu).meth2 et nu) sont non séquencées par rapport à les uns les autres selon la règle d'appel de fonction ci-dessus. Par conséquent, le effet secondaire du calcul de l'expression postfixe sur l'objet scalaire ar n'est pas séquencé par rapport à l'évaluation de l'argument de nu avant le meth2 Appel de fonction. D'après la règle d'exécution du programme ci-dessus, il s'agit d'un comportement non défini.

En d'autres termes, il n'est pas nécessaire que le compilateur évalue l'argument nu à l'appel meth2 Après l'appel meth1 - il est libre de ne supposer aucun effet secondaire de meth1 Affecte l'évaluation de nu.

Le code d'assemblage produit par ce qui précède contient la séquence suivante dans la fonction main:

  1. La variable nu est allouée sur la pile et initialisée avec 0.
  2. Un registre (ebx dans mon cas) reçoit une copie de la valeur de nu
  3. Les adresses de nu et c sont chargées dans les registres de paramètres
  4. meth1 Est appelé
  5. Le registre de valeur de retour et la valeur précédemment mise en cache de nu dans le registre ebx sont chargés dans les registres de paramètres
  6. meth2 Est appelé

De manière critique, à l'étape 5 ci-dessus, le compilateur permet à la valeur en cache de nu de l'étape 2 d'être réutilisée dans l'appel de fonction à meth2. Ici, il ne tient pas compte de la possibilité que nu ait pu être changé par l'appel à meth1 - 'comportement indéfini' en action.

REMARQUE: Cette réponse a changé en substance par rapport à sa forme originale. Mon explication initiale en termes d'effets secondaires du calcul d'opérande n'étant pas séquencé avant l'appel de fonction final était incorrecte, car elles le sont. Le problème est le fait que le calcul des opérandes eux-mêmes est séquencé de façon indéterminée.

28
Smeeheey

Dans la norme C++ de 1998, section 5, paragraphe 4

Sauf indication contraire, l'ordre d'évaluation des opérandes des opérateurs individuels et des sous-expressions des expressions individuelles, ainsi que l'ordre dans lequel les effets secondaires se produisent, n'est pas spécifié. Entre le point de séquence précédent et suivant, un objet scalaire doit voir sa valeur stockée modifiée au plus une fois par l'évaluation d'une expression. En outre, la valeur antérieure ne doit être consultée que pour déterminer la valeur à stocker. Les exigences du présent paragraphe doivent être satisfaites pour chaque classement autorisé des sous-expressions d'une expression complète; sinon, le comportement n'est pas défini.

(J'ai omis une référence à la note de bas de page # 53 qui n'est pas pertinente pour cette question).

Essentiellement, &nu Doit être évalué avant d'appeler c1::meth1() et nu doit être évalué avant d'appeler c1::meth2(). Il n'est cependant pas nécessaire que nu soit évalué avant &nu (Par exemple, il est permis que nu soit évalué en premier, puis &nu, Et ensuite c1::meth1() est appelée - ce qui pourrait être ce que fait votre compilateur). L'expression *ar = 1 Dans c1::meth1() n'est donc pas garantie d'être évaluée avant que nu dans main() soit évaluée, afin d'être passée à c1::meth2().

Les normes C++ ultérieures (que je n'ai pas actuellement sur le PC que j'utilise ce soir) ont essentiellement la même clause.

9
Peter

Je pense que lors de la compilation, avant que les fonctions meth1 et meth2 ne soient vraiment appelées, les paramètres leur ont été transmis. Je veux dire quand vous utilisez "c.meth1 (& nu) .meth2 (nu);" la valeur nu = 0 a été transmise à meth2, donc peu importe que "nu" soit modifié en dernier.

vous pouvez essayer ceci:

#include <iostream> 
class c1
{
public:
    c1& meth1(int* ar) {
        std::cout << "method 1" << std::endl;
        *ar = 1;
        return *this;
    }
    void meth2(int* ar)
    {
        std::cout << "method 2:" << *ar << std::endl;
    }
};

int main()
{
    c1 c;
    int nu = 0;
    c.meth1(&nu).meth2(&nu);
    getchar();
}

il obtiendra la réponse que vous souhaitez

6
T-shirt saintor