Partage
  • Partager sur Facebook
  • Partager sur Twitter

Constructeur d'une derived class deleted

    17 janvier 2024 à 20:53:25

    Salut tout le monde !

    Ce code compile :

    #include <iostream>
    
    
    class Base {
      public:
        int nb_;
    };
    
    class Derived : public Base {
    };
    
    int main() {
        Derived derived;
    
        return 0;
    }

    Mais bizarrement lorsque je définis un constructeur dans la classe Base, ça ne compile plus :

    #include <iostream>
    
    
    class Base {
      public:
        Base(int nb) :
            nb_(nb) {}
        int nb_;
    };
    
    class Derived : public Base {
    };
    
    int main() {
        Derived derived;
    
        return 0;
    }
    main.cpp: In function ‘int main()’:
    main.cpp:15:13: error: use of deleted function ‘Derived::Derived()’
       15 |     Derived derived;
          |             ^~~~~~~
    main.cpp:11:7: note: ‘Derived::Derived()’ is implicitly deleted because the default definition would be ill-formed:
       11 | class Derived : public Base {
          |       ^~~~~~~
    main.cpp:11:7: error: no matching function for call to ‘Base::Base()’
    main.cpp:6:5: note: candidate: ‘Base::Base(int)’
        6 |     Base(int nb) :
          |     ^~~~
    main.cpp:6:5: note:   candidate expects 1 argument, 0 provided
    main.cpp:4:7: note: candidate: ‘constexpr Base::Base(const Base&)’
        4 | class Base {
          |       ^~~~
    main.cpp:4:7: note:   candidate expects 1 argument, 0 provided
    main.cpp:4:7: note: candidate: ‘constexpr Base::Base(Base&&)’
    main.cpp:4:7: note:   candidate expects 1 argument, 0 provided

    Si j'ajoute un argument au constructeur, ça ne fonctionne pas non plus :

    #include <iostream>
    
    
    class Base {
      public:
        Base(int nb) :
            nb_(nb) {}
        int nb_;
    };
    
    class Derived : public Base {
    };
    
    int main() {
        Derived derived(9);
    
        return 0;
    }
    main.cpp: In function ‘int main()’:
    main.cpp:15:22: error: no matching function for call to ‘Derived::Derived(int)’
       15 |     Derived derived(9);
          |                      ^
    main.cpp:11:7: note: candidate: ‘constexpr Derived::Derived(const Derived&)’
       11 | class Derived : public Base {
          |       ^~~~~~~
    main.cpp:11:7: note:   no known conversion for argument 1 from ‘int’ to ‘const Derived&’
    main.cpp:11:7: note: candidate: ‘constexpr Derived::Derived(Derived&&)’
    main.cpp:11:7: note:   no known conversion for argument 1 from ‘int’ to ‘Derived&&’

    Ok, dans mon exemple l'erreur est assez claire :

    ‘Derived::Derived()’ is implicitly deleted because the default definition would be ill-formed

    Le compilateur a implicitement delete mon constructeur parce qu'il aurait été "mal défini" sinon.

    Seulement j'ai du mal à comprendre pourquoi le C++ ne permet pas de faire ce que j'essaie de faire ?

    Un constructeur par défaut aurait très bien pu être généré avec nb initialisé à 0.

    Je sais que dans mon exemple la situation paraît hyper claire et évidente mais cette particularité du C++ m'a déjà fait perdre pas mal de temps dans des plus gros projets avec des classes qui héritent d'un paquet d'autres et des appels implicites qui se font derrière le rideau. On se retrouve alors avec le constructeur qui se fait delete en cachette et une compilation qui rate. :euh:

    En plus, je crois ne jamais avoir vu dans une documentation du C++ que le constructeur d'une classe derived est delete dans le cas que je présente mais je me trompe peut-être...

    -
    Edité par ThomasAirain 17 janvier 2024 à 20:56:48

    • Partager sur Facebook
    • Partager sur Twitter
      17 janvier 2024 à 21:09:53

      C'est pourtant un classique de la POO C++ que certaines fonctions particulières sont générées ou supprimées en fonction du contexte. Cf :

      (source : https://youtu.be/vLinb2fgkHk )

      Pour résumer et simplifier : utilise default et delete pour avoir une déclaration explicite, comme ça, tu n'auras plus de surprise.

      EDIT :

      ThomasAirain a écrit:

      Le compilateur a implicitement delete mon constructeur 

      En réalité, c'est l'inverse : le compilateur a généré automatiquement le constructeur dans le premier code (implicite declaration implicite definition) et il a juste arrêté de faire une déclaration implicite dans le second code.

      -
      Edité par gbdivers 17 janvier 2024 à 21:23:53

      • Partager sur Facebook
      • Partager sur Twitter
        18 janvier 2024 à 12:10:29

        Je rajouterai qu'il n'y a aucune raison de supposer qu'il faille mettre nb à 0 (alors que l'on sait tous que la réponse est 42) vu que nulle part tu le dis.

        Après, pour expliquer le pourquoi du comment. Dans la classe Base tu explicites qu'il existe un constructeur qui permet de positionner correctement les invariants de la classe. Celui avec un paramètre. De fait le compilateur n'est pas autorisé à supposer qu'il pourrait fournir implicitement le constructeur par défaut car comment peut-il présupposer les invariants de notre classe?

        • Partager sur Facebook
        • Partager sur Twitter
        C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
          18 janvier 2024 à 20:13:13

          Ce que tu souhaites, semble être : je pose que pour créer un Derived(n) c'est comme pour créer un Base(n). C'est une possibilité, mais il y en a d'autres, et ça n'est pas au compilateur de choisir; c'est à toi de l'indiquer par:

          class Derived : public Base {
          public:
             using Base::Base;  // créer Derived c'est directement créer Base avec les mêmes paramètres
          };

          Tu pourrais aussi choisir; créer Derived() c'est créer Base(0). Il te faut alors écrire:

          class Derived : public Base {
          public:
             Derived() : Base(0) {}  // Pour créer Derived sans paramètre, mais sa Base a besoin d'un paramètre obligatoire
          };

          Ou d'autres cas peuvent être choisis, mais comme tu ne définis pas comment Derived doit être construit, le compilateur ne choisit pas pour toi.



          • Partager sur Facebook
          • Partager sur Twitter

          En recherche d'emploi.

            4 février 2024 à 1:21:10

            Dans C++, il est indiqué que le constructeur d'une derived class appelle le contructeur de la base class dans la initializer list de la derived class. Exemple :

            #include <string>
            
            
            class Character {
             public:
              Character(const std::string& name, int age, int healthPoints) :
                name_(name),
                age_(age),
                healthPoints_(healthPoints) {}
              virtual ~Character() = default;
            
             protected:
              std::string name_;
              int age_;
              int healthPoints_;
            };
            
            
            class Wizard : public Character {
             public:
              Wizard(const std::string& name, int age, int healthPoints, int mana) :
                Character(name, age, healthPoints),
                mana_(mana) {}
            
             private:
              int mana_;
            };
            
            
            int main() {
              Wizard wizard1("Antonidas", 77, 800, 1200);
            
              return 0;
            }

            Ici, l'appel du constructeur de la base class est on ne peut plus clair et sans équivoque parce que l'appel est implicite.

            Néanmoins, C++ Primer n'explique pas comment les constructeurs de derived class qui n'appellent pas explicitement le constructeur de la base class procèdent alors qu'il y a des cas où cela fonctionne. Ce qui peut laisser penser que l'appel du constructeur de la base classe n'est pas obligatoire. C'est à cause de cette omission (volontaire ou involontaire) que je comprenais pas mon erreur. :waw:

            Du coup j'imagine que s'il n'y a pas d'appel explicite du constructeur de la base class, il y a un appel IMPLICITE du constructeur par défaut. Or s'il n'y a pas de constructeur par défaut (notamment parce qu'un autre constructeur a été déclaré) le compilateur se retrouve sans issu et rate la compilation. Ce qui provoque l'erreur que j'ai expérimentée au départ.

            -
            Edité par ThomasAirain 4 février 2024 à 1:22:48

            • Partager sur Facebook
            • Partager sur Twitter
              4 février 2024 à 12:52:53

              ThomasAirain a écrit:

              Du coup j'imagine que s'il n'y a pas d'appel explicite du constructeur de la base class, il y a un appel IMPLICITE du constructeur par défaut. Or s'il n'y a pas de constructeur par défaut (notamment parce qu'un autre constructeur a été déclaré) le compilateur se retrouve sans issu et rate la compilation. Ce qui provoque l'erreur que j'ai expérimentée au départ.

              C'est exactement cela.

              Il faut ajouter que dans le cas où il y a héritage virtuel, l'appel implicite ne se fait plus. Il faut explicitement appeler le constructeur de la base virtuelle dans toute classe en dérivant (et y compris dans la cas où une classe intermédiaire a fait un appel explicite.)

              struct Base {
                 Base( std::string name ) : name(name) {}
                 std::string  name;
              };
              
              struct Intermediaire : public virtual Base {
                  Intermediaire( std::string name, int age )
                  : Base(name), age{age}  {}
                  int  age;
              };
              
              struct Finale : public Intermediaire {
                  Finale()
                  : Base( "Homme" ),          // obligatoire car virtuelle
                    Intermediaire( "X", 20 )  // obligatoire car pas de ctor par défaut (et name n'est pas utilisé!)
                  {}
              };
              • Partager sur Facebook
              • Partager sur Twitter

              En recherche d'emploi.

              Constructeur d'une derived class deleted

              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
              • Editeur
              • Markdown