Partage
  • Partager sur Facebook
  • Partager sur Twitter

surcharge méthode

Sujet résolu
    17 août 2018 à 0:05:09

    Bonjour à tous. Je pensais avoir compris la notion d'héritage et de surcharge de méthode dans une classe dérivée mais visiblement pas très bien. Par exemple le code suivant ne compile pas et je bloque complètement:

    #include <iostream>
    
    using namespace std;
    
    class A
    {
    public:
    	A()
    	{
    	}
    	virtual ~A()
    	{
    	}
    	
    	void show()
    	{
    		cout << "ok" << endl;
    	}
    };
    
    class B: public A
    {
    public:
    	B(): A()
    	{
    	}
    	~B()
    	{
    	}
    	
    	void show(int x)
    	{
    		cout << "val: " << x << endl;
    	}
    };
    
    int main(int argc, char** argv)
    {
    	B u;
    	
    	u.show();
    	u.show(5);
    	
    	return 0;
    }

    En effet, ici ma classe A définie bien une méthode "show()" et normalement en écrivant

    class B: public A

    je m'attends à ce que B puisse accéder à la méthode "A::show()". Pour autant à la compilation j'obtiens une erreur

    error: no matching function for call to ‘B::show()’
      u.show();
             ^
    note: candidate: void B::show(int)
      void show(int x)
    



    À tous les coup il s'agit d'une subtilité que je ne saisi pas encore. Merci pour vos lumières!


    -
    Edité par Revenant 17 août 2018 à 0:07:17

    • Partager sur Facebook
    • Partager sur Twitter
      17 août 2018 à 0:17:48

      Ca s'appelle le masquage de fonction. Cf https://en.cppreference.com/w/cpp/language/qualified_lookup 

      En gros, la resolution des noms de fonctions s'arrete quand le nom est trouve dans la classe de base. Pour aller recuperer la fonction de la classe parent, il faut :

      • soit specifier la classe (qualified name) :
      int main(int argc, char** argv)
      {
          B u;
           
          u.A::show();
          u.show(5);
           
          return 0;
      }
      • soit exporter la fonction de la classe parent dans la classe derivee :
      class B: public A
      {
      public:
          B(): A()
          {
          }
          ~B()
          {
          }
          
          using A::show;
          
          void show(int x)
          {
              cout << "val: " << x << endl;
          }
      };

      Hors sujet : beaucoup de code inutile dans ce que tu as ecris :

      class A
      {
      public:
          virtual ~A() = default;
           
          void show()
          {
              cout << "ok" << endl;
          }
      };
       
      class B: public A
      {
      public:
          using A::show;
          
          void show(int x)
          {
              cout << "val: " << x << endl;
          }
      };
      • pas besoin de definir les constructeurs par defaut s'ils font rien de particulier (si tu ne les mets pas, le compilateur les cree automatiquement)
      • utilises "= default" pour definir le destructeur en virtuel
      • Partager sur Facebook
      • Partager sur Twitter
        17 août 2018 à 9:08:33

        Super ! Merci beaucoup.

        Au sujet des constructeurs par défaut, je me penche en ce moment sur la lecture d'un bouquin et il parle justement de "Rule of three" qui me semble être ce que tu mentionnes.

        Encore une fois merci!

        • Partager sur Facebook
        • Partager sur Twitter
          17 août 2018 à 9:31:46

          Et bien pas du tout ! :D

          Enfin, c'est plus compliqué.

          Le compilateur est capable de générer certaines fonction :

          • le constructeur par défaut
          • le destructeur par défaut
          • le constructeur par copie
          • le constructeur par mouvement
          • l’opérateur d'affectation par copie
          • l’opérateur d'affectation par mouvement

          On parle de fonctions "implicitement déclarées et implicitement définies".

          Il existe des règles qui disent quand le compilateur peut ou pas générer ces fonction. Par exemple, si tu définies un constructeur, le constructeur par défaut n'est plus générés automatiquement.

          Dans certains cas, tu veux interdire explicitement ces fonctions automatiquement générées, c'est le rôle de "delete".

          class A {
              A() = delete;
          };

          Dans d'autres cas, tu dois déclarer explicitement une fonction, mais tu veux utiliser l’implémentation automatique par le compilateur. C'est le rôle de "default". On parle de fonction "explicitement déclarées et implicitement définies".

          class A {
              A() = default; // n'est pas généré automatiquement a cause de A(int)
              A(int);
          
              virtual ~A() = default; // on veut le déclarer virtual et utiliser l’implémentation automatique
          };

          Maintenant, la règle des 3" (ton livre est vieux... maintenant, on parles de la règles des 5 et de la règle du 0).

          Le principe est que l’implémentation des fonctions que j'ai cité (sauf le constructeur par défaut) sont liées. Si on écrit par exemple l’opérateur d'affectation par copie, c'est généralement parce qu'il y a un attribue qui doit être géré manuellement. Et dans on devrait aussi écrire les autres fonctions, pour gérer cet attribue. Ces règles disent en gros : "si tu as besoin de modifier une de ces fonctions, alors tu dois toutes les modifier". (Avec une grosse exception : le destructeur virtuel). Ce sont des fonctions qui sont "explicitement déclarées et explicitement définies".

          La règle des 3 : avec le C++11, il n'y avait pas de sémantique de mouvement, donc il fallait réécrire le destructeur et les 2 fonctions de copie

          La règle des 5 : après le C++11, il faut réécrire le destructeur, les 2 fonctions de copie, et les 2 fonctions de mouvement

          Mais en fait, gérer manuellement la mémoire est une responsabilité a part entière. Et avoir une classe qui gère la mémoire et qui fait autre chose, c'est une chose de trop a faire. (Principe de responsabilité unique). Donc il faut séparer ces responsabilités dans 2 classes : une qui géré la mémoire (avec le RAII) et une qui fait ce qu'elle doit faire. 

          On a donc maintenant la règle du 0 : on doit redéfinir manuellement aucune des fonctions. La gestion de la mémoire doit être fait par des classes RAII. (Et la grande majorité du temps, les classes RAII proposées par la bibliothèque standard sont suffisante : std::vector, std::string, std::unique_ptr, std::shared_ptr, etc)

          -
          Edité par gbdivers 17 août 2018 à 9:34:09

          • Partager sur Facebook
          • Partager sur Twitter

          surcharge méthode

          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
          • Editeur
          • Markdown