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!
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
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.
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)
× 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.
Discord NaN. Mon site.
Discord NaN. Mon site.