Forum Programmation.c++ Bizarrerie opérateur ternaire (?:)

Posté par  (site web personnel) . Licence CC By‑SA.
2
10
juin
2020

Je suis tombé sur un comportement concernant l'opérateur ternaire C++ (?:) qui (me) semble bizarre. Il est peut-être tout à fait normal, compte tenu que trois compilateurs différents ont le même comportement, mais je ne trouve pas d'explication satisfaisante.

Il porte sur le passage, en tant que paramètres d'un opérateur ternaire, d'un objet déjà instancié, et de l'instanciation à la volée d'un objet du même type. Le destructeur de l'objet déjà instancié est appelé à priori sans que cela ai lieu d'être.

Voici le source d'un programme mettant ce comportement en exergue :

#include <iostream>

class my_class {
protected:
  char c_;
public:
  my_class(char c) {
    c_ = c;
    std::cout << "Ctor: " << c_ << std::endl << std::flush;
  }
  ~my_class() {
    std::cout << "Dtor: " << c_ << std::endl << std::flush;
  }
};

#define M(e)\
  std::cout << std::endl << "true ? " << #e << std::endl << std::flush;\
  (true ? e)

int main() {
  my_class A('A'), B('B');

  M(A : B);
  M( A : my_class('C') );
  M( my_class('D') : A );
  M( my_class('E') : my_class('F') );

  std::cout << std::endl << "End" << std::endl << std::flush;
}

Voici le résultat de ce programme. On y voit les paramètres de l'opérateur ternaire, et les éventuels appels aux constructeurs et/ou aux destructeurs.

Le texte en gras et italique est de mon fait, pour signaler là où je vois un problème.

Ctor: A
Ctor: B

true ? A : B

true ? A : my_class('C')
Dtor: A --> Pourquoi le destructeur de l'objet A est-il appelé ?

true ? my_class('D') : A
Ctor: D
Dtor: D

true ? my_class('E') : my_class('F')
Ctor: E
Dtor: E

End
Dtor: B
Dtor: A

Si vous voulez constater ce comportement de visu, il suffit de suivre ce lien : https://repl.it/@AtlasTK/C-bug

  • # Indice

    Posté par  (site web personnel) . Évalué à 2.

    Ajoute (un log dans) un constructeur de copie ?

    Rien de spécifique à l'opérateur ternaire, je dirais. ;)

    Debian Consultant @ DEBAMAX

    • [^] # Re: Indice

      Posté par  . Évalué à 2. Dernière modification le 11 juin 2020 à 11:33.

      L'indice de Cyril t'indique la bonne direction.

      J'ai refait ton truc avec un constructeur de copie et en virant les std::flush et les std::endl, parce que d'une part, dans endl il y a déjà un flush, et d'autre part, tu t'intéresses à la sortie standard du programme une fois terminé, alors tu n'as pas besoin de faire un flush à chaque évènement quantique. Du coup c'est moins lourd à lire.

      #include <iostream>
      
      class my_class {
        char c_;
      public:
        my_class(char c) {
          c_ = c;
          std::cout << "Ctor: " << c_ << '\n';
        }
      
        my_class(const my_class & rhs) : c_(rhs.c_) {
          std::cout << "Cpy: " << c_ << '\n';
        }
      
        ~my_class() {
          std::cout << "Dtor: " << c_ << '\n';
        }
      };
      
      #define M(e)\
        std::cout << "\ntrue ? " << #e << '\n';\
        (true ? e)
      
      int main() {
        my_class A('A'), B('B');
      
        M( A : B );
        M( A : my_class('C') );
        M( my_class('D') : A );
        M( my_class('E') : my_class('F') );
      
        std::cout << "\nEnd main()\n";
      }

      Comme l'indiquait Cyril, une copie est effectuée quand tu invoques true ? A : my_class('C') et pas dans les autres cas. Cette copie est nécessaire pour que les deux arguments de l'opérateur ternaire soient de même type (my_class).
      * Dans la ligne précédente, les deux arguments sont déjà du même type (my_class &) et aucune conversion n'est nécessaire ;
      * Dans la ligne suivante, une conversion serait nécessaire, mais elle n'a pas lieu parce qu'elle est codée dans la branche false de l'opérateur ternaire ;
      * Dans le dernier cas, les deux arguments sont de type (my_class) et aucune conversion n'est nécessaire.

      Un collègue sympa a réécrit le code avec des template pour mettre en évidence les types utilisés :

      #include <iostream>
      
      class my_class {
        char c_;
      public:
        my_class(char c) {
          c_ = c;
          std::cout << "Ctor: " << c_ << '\n';
        }
      
        my_class(const my_class & rhs) : c_(rhs.c_) {
          std::cout << "Cpy: " << c_ << '\n';
        }
      
        ~my_class() {
          std::cout << "Dtor: " << c_ << '\n';
        }
      };
      
      template <typename> char const* type();
      template <> char const* type<my_class&>() { return "my_class&"; }
      template <> char const* type<my_class const&>() { return "my_class const&"; }
      template <> char const* type<my_class>() { return "my_class"; }
      
      #define M(e)\
        std::cout << "\ntrue ? " << #e << '\n';\
        std::cout << "type=" << type<decltype(true? e)>() << "\n"; \
        (true ? e)
      
      int main() {
        my_class A('A'), B('B');
      
        M( A : B );
        M( A : my_class('C') );
        M( my_class('D') : A );
        M( my_class('E') : my_class('F') );
      
        std::cout << "\nEnd main()\n";
      }

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.