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

Le résultat de ce programme:

#include  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 pas 1 quand meth2() démarre?

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

Vous voyez que nu in main est évalué à 0 avant même que meth1 soit appelé. C’est le problème du chaînage. Je conseille de ne pas le faire.

Créez simplement un programme 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 des opérandes des opérateurs individuels et des sous-expressions des 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 la valeur du résultat de l’opérateur. Si un effet secondaire sur un object scalaire est non séquencé par rapport à un autre effet secondaire sur le même object scalaire ou à un calcul de valeur utilisant la valeur du même object scalaire et qu’ils ne sont pas potentiellement concurrents, le comportement est indéfini

et aussi:

5.2.2 Appel de fonction

  1. [Note: les évaluations de l’expression postfixe et des arguments sont toutes non séquencées les unes par rapport aux autres. Tous les effets secondaires des évaluations d’argument sont séquencés avant la saisie de la fonction – 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 décomposition dans l’expression postfix et l’argument nu :

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

Les évaluations de l’expression et de l’argument postfix pour l’appel de fonction final (c’est-à-dire l’expression postfixe c.meth1(&nu).meth2 et nu ) ne sont pas séquencées l’une par rapport à l’autre selon la règle d’ appel de fonction ci-dessus. Par conséquent, l’ effet secondaire du calcul de l’expression postfixe sur l’object scalaire ar n’est pas séquencé par rapport à l’évaluation de l’argument de nu avant l’ meth2 fonction meth2 . Par la règle d’ exécution du programme ci-dessus, il s’agit d’un comportement indé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 supposer qu’aucun effet secondaire de meth1 n’affecte l’évaluation 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 stack 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 parameters
  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 des registres de parameters
  6. meth2 s’appelle

De manière critique, à l’étape 5 ci-dessus, le compilateur permet de réutiliser la valeur en cache de nu de l’étape 2 dans l’appel de fonction à meth2 . Ici, il ignore la possibilité que nu ait été modifié par l’appel à meth1 – «comportement indéfini» en action.

NOTE: Cette réponse a changé en substance de sa forme originale. Mon explication initiale en termes d’effets secondaires du calcul d’opérande n’étant pas séquencés avant l’appel de fonction final était incorrecte, car ils le sont. Le problème est le fait que le calcul des opérandes eux-mêmes est indéfiniment séquencé.

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, ne sont pas spécifiés. Entre le sharepoint séquence précédent et suivant, un object scalaire aura sa valeur stockée modifiée au plus une fois par l’évaluation d’une expression. En outre, la valeur antérieure doit être accessible uniquement pour déterminer la valeur à stocker. Les exigences de ce paragraphe doivent être satisfaites pour chaque ordre autorisé des sous-expressions d’une expression complète; sinon le comportement est indéfini.

(J’ai omis une référence à la note de bas de page n ° 53 qui ne concerne pas cette question).

Essentiellement, &nu doit être évalué avant d’appeler c1::meth1() , et nu doit être évalué avant d’appeler c1::meth2() . Il n’y a cependant aucune exigence pour que nu soit évalué avant &nu (par exemple, il est permis que nu soit évalué en premier, ensuite &nu , et ensuite c1::meth1() est appelé – ce qui peut ê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 in main() soit évalué, pour être passé à c1::meth2() .

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

Je pense que lors de la compilation, avant que les fonctions meth1 et meth2 ne soient réellement appelées, les parameters leur ont été transmis. Je veux dire quand vous utilisez “c.meth1 (& nu) .meth2 (nu);” la valeur nu = 0 a été passée à meth2, donc peu importe si “nu” a été modifié.

vous pouvez essayer ceci:

 #include  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 voulez