Lors d'un test pour un entretien je suis tombé sur ce cas que je n'ai pas compris sur le moment :
class Base
{
protected:
string name;
public:
Base(string _name) : name(_name) {};
string getName() const {return name;};
};
class A : public Base
{
protected:
string strA;
public:
A(string _name, string _str) : Base(_name), strA(_str) {};
string getStrA() const {return strA;};
};
class B : public Base
{
protected:
string strB;
public:
B(string _name, string _str) : Base(_name), strB(_str) {};
string getStrB() const {return strB;};
};
class Last : public A, public B
{
private:
string strC;
public:
Last(string _name, string _strA, string _strB, string _strC)
: A(_name, _strA), B(_name, _strB), strC(_strC) {};
string getStrC() const {return strC;};
};
int main() {
Last last("Georges", "STR_A", "STR_B", "STR_C");
cout << last.getName() << endl;
cout << last.getStrA() << endl;
cout << last.getStrB() << endl;
cout << last.getStrC() << endl;
return 0;
}
Auniveau de :
cout << last.getName() << endl;
CLion me dit :
Non-static member 'getName' found in multiple base-class subobjects of type 'Base':
class Last -> class A -> class Base
class Last -> class B -> class Base
member found by ambiguous name lookup
Je comprends qu'en effet, getName pose problème car ET la classe A ET la class B vont initialiser cette fonction.
Maintenant la question que je me pose, c'est est-ce que c'est possible de créer une classe qui hérite de 2 autres classes qui elles mêmes héritent d'une même class ?
Merci !
- Edité par BobbyLa Courgette 31 mars 2021 à 17:29:27
L'exemple minimum (au lieu de nous poser la question...)
class A {};
class B1 : public A {};
class B2 : public A {};
class C : public B1, public B2 {};
int main() {
C c;
return 0;
}
En développant un peu, si il y a une fonction dans A
class A {
public:
void foo() {
std::cout << "Hello" << std::endl;
}
};
elle sera héritée deux fois, donc pour l'appeler, il faudra lever l’ambiguïté pour dire laquelle des deux
int main() {
C c;
c. B1::foo ();
}
-------
Autre exemple, en héritant deux fois d'une même classe template
#include <iostream>
template<typename T>
class Boite {
public:
T contenu;
};
class GrosseBoite : public Boite<int>, public Boite<double>
{
};
int main() {
GrosseBoite t;
t. Boite<double>::contenu = 3.14;
t. Boite<int>::contenu = 1234;
std::cout << t.Boite<double>::contenu << std::endl;
std::cout << t.Boite<int>::contenu << std::endl;
}
PS: je ne recommande pas de programmer des choses de ce genre (de l'héritage d'implementation => composition), je montre juste comment résoudre l'ambiguité.
Là, typiquement, tu te retrouve avec ce que l'on appelle le "losange de la mort", car Last hérite de A et de B, ce qui implique que tu trouve dans Last tout ce qui vient de A et tout ce qui vient de B.
Le problème, c'est que tu trouves dans A tout ce qui vient de Base, vu que A hérite de base et que, dans le même temps, tu trouves aussi dans B tout ce qui vient de Base pour la même raison.
Tu trouves donc dans Last une std::string appelée name qui vient de la partie Base issue de A mais aussi une std::string appelée name qui vient de la partie Base ... issue de B.
Et, bien sur, il en va de même avec la fonction membre getName.
A ce stade, le compilateur n'y voit pas grand chose à dire, pour la simple et bonne raison qu'il est parfaitement en mesure de distinguer la std::string name (et le fonction membre getName) qui vient de A et celle(s) qui vien(nen)t de B.
Là où cela va poser problème, c'est quand tu voudras justement faire appel à la fonction getName -- ou quand tu voudras modifier la std::string name -- car le compîlateur ne saura pas déterminer "comme cela" si tu veux appeler la fonction getName (respectivement, si tu veux modifier la donnée name) qui vient de A ou celle qui vient de B.
Alors, il y a, bien sur, moyen de "contenter" le compilateur en lui donnant l'information dont il a besoin avec un code qui pourrait ressembler à
#include <iostream>
#include <string>
/* je vais éviter le couplet sur la sémantique d'entité
* qu'il faudrait donner aux classes dérivées de Base,
* ce sera pour une autre fois
*/
class Base{
public:
Base(std::string const & name):name_{name}{
}
std::string const & name() const{
return name_;
}
private:
std::string name_;
};
class A : public Base{
public:
A(std::string const & name, int valA):
Base{name},valA_{valA}{
}
int valA() const{
return valA_;
}
private:
int valA_;
};
class B : public Base{
public:
B(std::string const & name, int valB):
Base{name},valB_{valB}{
}
int valB() const{
return valB_;
}
private:
int valB_;
};
class Last : public A, public B{
public:
Last(std::string const & name, int valA, int valB):
A{name, valA}, B{name, valB}{
}
};
int main(){
Last l{"hello", 3, 5};
std::cout<<l.A::name()<<"\n"
<<l.B::name()<<"\n";
}
(j'ai utilisé des noms "à moi", mais, dans l'ensemble, c'est exactement le même code que le tien )
qui réagira exactement de la manière dont on est en droit de d'y attendre: en affichant le nom à partir des deux fonctions name:
(la compilation, sous linux, mais cela n'a acune espèce d'importance)
$ g++ main.cpp
(le résultat obtenu à partir de cette compilation)
./a.out
hello
hello
Seulement, voilà : Si l'on prend la peine d'y réfléchir quelques secondes, il semble très rapidement évident que notre classe Last n'a absolument aucun besoin de disposer deux fois de la donnée name (ni d'exposer deux fois la fonction getName); comme c'est le cas pour l'instant.
Une des solutions possibles (la plus mauvaise) serait d'avoir ecours à ce que l'on appelle "l'héritage virtuel". Cela consiste -- en gros -- à dire au compilateur que, bien que les classes A et B héritent tous les deux de la classe Base, il ne faudra jamais qu'une seule partie issue de la classe Base dans toutes les classes qui dériveront soit de A, soit de B, soit même des deux.
Pour simplifier, on pourrait dire que, si la partie correspondant à la classe Base doit être présente, quelle que soit la raison, elle ne doit appartenir à aucune des classes qui est sensée l'amener en particulier.
Le truc, c'est que, comme, du coup, ce ne plus ni à la classe A ni à la classe B "en particulier" de créer la partie correspondant à la classe Base, hé bien, il faudra bien s'assurer que la partie correspondant à la classe Base soit malgré tout construite dans toutes les classes qui héritent soit de A, soit de B (soit des deux).
NOTA: il va de soi que, si tu avais les classes C, D, E, F et G qui héritent elles aussi de Base, le principe serait tout à fait le même
Cela nous amènerait à un code ressemblant à quelque chose comme
#include <iostream>
#include <string>
class Base{
public:
Base(std::string const & name):name_{name}{
}
std::string const & name() const{
return name_;
}
private:
std::string name_;
};
class A : virtual public Base{
public:
A(std::string const & name, int valA):
Base{name},valA_{valA}{
}
A(int valA):A{"A sans nom",valA}{
}
int valA() const{
return valA_;
}
private:
int valA_;
};
class B : virtual public Base{
public:
B(std::string const & name, int valB):
Base{name},valB_{valB}{
}
B(int valB):B{"B sans nom",valB}{
}
int valB() const{
return valB_;
}
private:
int valB_;
};
class Last : public A, public B{
public:
Last(std::string const & name, int valA, int valB):
A{valA}, B{valB}, Base{name}{
}
};
int main(){
Last l{"hello", 3, 5};
std::cout<<l.name()<<"\n";
}
Un tel code va compiler sans problème et va même nous fournir le résultat attendu. Nous pourrions donc croire que "tout va pour le mieux dans le meilleur des mondes", n'est ce pas?
En fait, non. Car il faut être conscient que cette approche va -- très vraisemblablement -- créer beaucoup plus de problèmes sur le long terme qu'elle ne va permettre d'en résoudre dans l'immédiat.
Je ne vais pas m'étendre particulièrement sur ce point (car mon intervention sera déjà assez longue ainsi), à part pour te dire que la relation d'héritage est la relation la plus forte qui puisse exister en programmation. Ce qui implique -- pour faire simple -- que c'est aussi la relation que tu devrais utiliser le moins souvent et ... uniquement si tu n'as vraiment pas d'autre choix.
Et donc, la bonne question à se poser serait plutôt du genre "pourquoi voudrais tu que A et B héritent de Base ?"
Tu as peut-être des raisons qui te semblent bonnes pour le faire, mais, comme tu ne nous donne aucune indication quant au contexte dans lequel tu voudrais le faire, il nous est totalement impossible de t'expliquer comment t'en passer
Ah, oui, mais non... Tu nous dis que c'était au cours d'un entretien
On peut donc présumer que la personne qui t'a posé cette question s'attendait effectivement à voir apparaitre l'héritage virtuel.
D'un autre coté, si tu veux un avis personnel, tu devrais à tout prix éviter de travailler pour cette personne car sa compétence laisse grandement à désirer
elle utilise de toute évidence la directive using namespace std; de manière globale, ce qui n'est plus recommandé depuis au moins vingt ans (cf cette intervention de ma part sur un autre forum)
elle utilise l'accessibilité protégée pour placer des données, alors que les données -- si elles ne sont pas publiques -- ne devraient pouvoir se trouve que dans l'accessibilité privée (quitte à avoir des fonctions qui y accèdent dans l'accessibilité protégée)
les sémantiques de valeurs et d'entités lui semblent totalement inconnues
j'en oublie peut-être
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Il n'a pas la prétention d'être une solution à un problème, ni la démonstration d'une bonne pratique.
Pourquoi voudriez-vous de classes qui s'appellent A et B ? :-)
En interview, la question permet de différencier rapidement ceux qui l'ont regardé de près, et ceux qui se contentent de transposer ce qu'ils connaissent en Java ou PHP, ou qui ne sont pas allé jusque-là dans leur étude de C++.
Ca peut être critique si, dans la boite, ils programment en C++ en combinant des "mixins" (petite classe qui fournit une responsabilité).
Il est aussi possible que la boite s'en foute, mais qu'il faille éliminer rapidement ceux pour qui la prog objet est une histoire d'oiseaux qui héritent des animaux. Déjà, si le type ne dit pas de suite "ah oui, héritage en diamant", on est fixé.
Ok, très interessant, beaucoup de choses que je n'avais pas vu ou que je ne connaissais tout simplement pas.
@koala01 :
1) pour le using namespace , oui quand je code des trucs à la con, juste pour faire des tests, je mets tout le temps cette directive les "std::" partout ca va bien deux secondes mais par contre tu valides "using std::cout" par exemple ou pas non plus ?
2) Qu'est ce qui te deranges avec les protected ? Si la classe "voiture" hérite de "véhicule" ayant pour variable le "nom" de ce véhicule, tu veux que voiture demande la permission (std::string getName() const) a véhicule pour récupérér son propre nom ? Je trouve ca étrange.
3) Les sémantiques (je viens de lire ce que c'est à l'instant) m'échappait à moi aussi et j'imagine que tu dis ca car A et B sont les mêmes classes ? Ce n'est pas un copié collé de son test, c'est moi qui l'ai reproduit pour comprendre ce qui m'échappait.
Ce que le recruteur attendait de cette partie en effet c'était que je mette ce virtual et que j'adapte le code, ce que je n'ai pas pu faire car je n'ai jamais eu l'occasion dans mes codes de tomber sur ce cas de figure d'héritage en losange ou en diamand.
En tout cas merci à tous pour ces explications plus que claires !
La question de l'héritage en diamant est un probleme plutot théorique lié à la notion d'héritage multiple.
En pratique il se pose rarement en partant d'une conception raisonnable, où on ne s'est pas amusé à faire de l'héritage quand il s'agit de composition. (Une voiture n'est pas un moteur avec des accessoires)
Donc il y a peu de chances d'y être confronté dans de l'auto apprentissage. C'est à ça qu'on reconnaît ceux qui ont eu des cours/lu des bouquins où on leur a exposé le truc (et qui s'en souviennent).
---
La problématique derrière, c'est la philosophie choisie par un langage pour s'en sortir. En C++, on desambigue. Dans d'autres langages, on peut imaginer de prendre dans la première super classe qui vient. En java, on évite le problème : pas d'héritage multiple, on se débrouille avec une classe parente + des interfaces.
1) pour le using namespace , oui quand je code des trucs à la con, juste pour faire des tests, je mets tout le temps cette directive les "std::" partout ca va bien deux secondes mais par contre tu valides "using std::cout" par exemple ou pas non plus ?
De manière générale, j'aurais tendance à ne pas le valider si c'est de manière globale.
La raison est simple: qu'est ce qui te dit que tu ne vas pas vouloir jouer avec une variable nommée "cout" parce que tu as décidé d'utiliser des identifiants en francais?
Par contre, je suis beaucoup moins réticent face à une utilisation dans un espace très limité, s'il y a la garantie que cela ne posera aucun problème
BobbyLa Courgette a écrit:
2) Qu'est ce qui te deranges avec les protected ?
Ce qui me dérange avec les données en accessibilité protégée, c'est que les données doivent être considérées comme des éléments qui subissent de pleins fouet des préconditions et des postconditions imposées par la classe dans laquelle elles se trouvent.
C'est donc la classe de base qui doit prendre toutes les dispositions nécessaires pour s'assurer que, lorsque l'on essaye de modifier la valeur d'une donnée, ces conditions soient bel et bien respectées, autrement, la modification ne peut tout simplement pas se faire.
Si tu places tes données dans l'accessibilité protégée, tu permet aussi aux classes dérivées d'aller modifier la valeur de cette donnée alors que, en tant que développeur de la classe de base, tu ignore absolument tout de ces classes dérivées.
Tu déporte alors la responsabilité de t'assurer que ces conditions sont bien remplies au niveau des classes dérivées, avec le risque que celui qui développera la classe dérivée oublie de vérifier ces conditions ou qu'il oublie d'en oublier "quelques unes".
Au final, le fait de placer les données dans l'accessibilité protégée revient à peu près au même que de les placer dans l'accessibilité publique, la seule différence étant que le problème que cela peut poser est "à peine plus limité" quant au nombre d'éléments qui pourraient y accéder
BobbyLa Courgette a écrit:
Si la classe "voiture" hérite de "véhicule" ayant pour variable le "nom" de ce véhicule, tu veux que voiture demande la permission (std::string getName() const) a véhicule pour récupérér son propre nom ? Je trouve ca étrange.
Ben, ce qu'il faut comprendre d'abord et avant tout, c'est que, en tant que développeur, tu as systématiquement deux casquettes:
celle de développeur à proprement parler, pour les fonctionnalités que tu développe au moment où tu es occupé à le faire et
celle d'utilisateur des fonctionnalités qui ont déjà été développées, que ce soit par toi ou par "quelqu'un d'autre".
Pour te donner un exemple tout simple, tu peux décider d'utiliser std::vector, std::cout ou std::string le cadre d'une fonction particulière dont tu as besoin "par ailleurs".
Là, les choses sont simples: tu est -- très clairement -- l'utilisateur des fonctionnalités offertes par std::cout, std::vector ou std::string vu qu'elles font partie de la bibliothèque standard.
Mais, intéressons nous d'un peu plus près à la fonctionnalité que tu es justement occupé à développer, et qui requière l'utilisation des différentes notions offertes par la bibliothèque standard.
Cette "nouvelle fonctionnalité" ne va avoir du sens que si elle est utile et utilisée ... ailleurs dans le code et ce, que ce soit toi ou "quelqu'un d'autre" qui décide de l'utiliser.
Or, si c'est toi qui décide d'utiliser cette nouvelle fonctionnalité, tu en deviens de facto ... un utilisateur, et ce, même si c'est toi qui a développé cette fonctionnalité. Et, en tant qu'utilisateur, tu va -- forcément -- être limité par les contraintes que le développeur aura imposée lors du développement de cette fonctionnalité.
Le fait que ce soit toi qui ait développé cette fonctionnalité n'y changera absolument rien et ne t'apportera absolument aucun avantage par rapport aux autres utilisateurs, ne serait-ce que parce qu'il peut s'être écoulé "tellement de temps" entre le moment où tu as développé la fonctionnalité et celui où tu décide de l'utiliser que tu peux "tout aussi bien" avoir oublié la grosse majorité des décisions que tu as prises pendant le développement et des raisons pour lesquelles elles ont été prises.
D'ailleurs, il est "très probable" que, si on te demandait à ce moment là de développer une "nouvelle version" de la fonctionnalité en question sans t'inspirer du code existant, tu en vienne à le faire de manière totalement différentes, en prenant d'autres décisions pour d'autres raisons qui te sembleront tout aussi bonnes.
Donc, au final, ce qu'il faut bien comprendre, c'est que, quelle que soit la fonctionnalité que tu décides de développer, qu'il s'agisse d'une (hiérarchie de) classe(s) ou d'une "simple" fonction, tu n'en es réellement le développeur que le temps strictement nécessaire pour en écrire le code et pour t'assurer qu'elle "fonctionne comme on est en droit de l'attendre" (et éventuellement, le temps durant lequel tu en modifies le code pour l'améliorer ou pour corriger une erreur existante ).
Et donc, que dés que tu ne travailles plus sur le code d'une fonctionnalité, tu ne peux en être que ... l'utilisateur.
Or, en tant que développeur d'une fonctionnalité quelconque, il y a en permanence la loi de Murphy (l'utilisateur est un imbécile distrait qui n'attend que l'occasion de faire une connerie, et de préférence, au pire moment qui soit) qui se balade au dessus de ta tête comme l'épée de la justice, près à te tomber dessus de manière aveugle au moindre signe de problème.
Ton rôle est donc de tout faire pour... éviter "autant que faire se peut" à l'utilisateur de ta fonctionnalité de faire une connerie avec les données que ta fonctionnalité manipule.
Seulement, ce n'est pas toujours aussi facile à faire qu'à dire, car il est pour ainsi dire impossible de penser à tout. Si bien que, quand tu vas développer une fonctionnalité quelconque, tu vas prévoir une utilisation "de base" (l'utilisation "normale" pourrait on dire), et peut être un ou deux cas particuliers.
Manque de bol, tu trouveras toujours "un utilisateur" (ce sera peut-être toi) qui trouvera "une autre manière" d'utiliser ta fonctionnalité, à laquelle tu n'aurais -- bien évidemment (où serait le plaisir autrement? ) -- au moment où tu as développé ta fonctionnalité
Tout cela pour dire que c'est le développeur qui va devoir "baliser" l'utilisation qui pourra être faite non seulement de la fonctionnalité, mais aussi de toutes les informations que cette fonctionnalité peut manipuler et, le tout, sans même tenir compte des utilisations prévues et / ou prévisibles qui pourraient en être faites.
Par exemple, si tu développe une classe de base nommée Vehicle, tu peux déjà prévoir qu'il y aura une classe nommée Car qui en héritera. Voire, après un peu de réflexion, en arriver à la conclusion qu'il pourrait aussi y avoir des classes Truck et MotorCycle qui en héritent (car les voitures, les camions et les motos peuvent être considérées comme des véhicules, n'est-ce pas).
Mais dis moi, qu'en serait-il des classes WindSurf, Boat (car une planche à voile ou un bateau peuvent aussi être considérés comme des véhicules, n'est-ce pas?) ou de n'importe quel autre type de véhicule auquel on ne pense pas immédiatement ?
BobbyLa Courgette a écrit:
3) Les sémantiques (je viens de lire ce que c'est à l'instant) m'échappait à moi aussi et j'imagine que tu dis ca car A et B sont les mêmes classes ? Ce n'est pas un copié collé de son test, c'est moi qui l'ai reproduit pour comprendre ce qui m'échappait.
Non, même pas...
On parle ici de Base, classe A et classe B et c'est peut-être simplement trop générique que pour permettre de se faire une idée précise de ce dont nous sommes occupés à parler.
La sémantique des classes, c'est, pour donner un exemple concret, la différence fondamentale qui peut exister entre une Couleur d'une part et les classes Vehicule, Voiture et Moto d'autre part.
Car la classe Couleur (ou la classe Distance, Duree, Date, Quantité et tant d'autres auxquelles je ne pense pas dans l'immédiat) va présenter une sémantique de valeur.
C'est à dire qu'il n'y aura aucun problème si on se retrouve, à un instant T bien précis de l'exécution, avec deux données de type Couleur qui se trouvent à des endroits différents en mémoire et qui présentent, pourtant, exactement les mêmes valeurs.
Si bien que l'on peut dire que la classe Couleur (et, de manière générale, toutes les classes présentant une sémantique de valeur)
peut voir une de ses instance assignée à une autre instance (on peut décider à tout moment de changer la couleur de la salle de bains, par exemple)
peut être copiée (on peut décider de repeindre la salle de bains dans la même couleur que celle de la chambre)
peut voir une instance particulière comparée -- ad minima par égalité -- avec une autre (on peut comparer la couleur de la salle de bains avec celle de la chambre pour savoir si ce sont les mêmes)
(d'un avis perso, sujet à débat) devrait être a priori immuable, car, si je modifie ne serait-ce qu'un tant soit peu la proportion de rouge, de vert et de bleu, j'obtiens une couleur différente (NOTA: beaucoup estiment que ce genre de classe est "plus facile à manipuler" si l'on autorise quand même la "mutabilité". Je crois que c'est vraiment à déterminer au cas par cas: une classe "ChaineDeCaractères" étant sans doute plus susceptible d'être mutable qu'une classe "Couleur" )
ne peut pas intervenir dans une hiérarchie de classe (Même si on peut prévoir plusieurs types de couleur, comme RGB, niveaux de gris ou CMJN, ce sont autant de types de couleurs sans lien entre eux, hormis la possibilité éventuelle de convertir l'un en l'autre)
A l'inverse, les classes Vehicule, Voiture, Camion et moto (ou Batiment, Maison, Usine et Hangard ou CompteEnBanque, CompteCourant et CompteDepargne ou tant d'autres auxquelles je ne pense pas dans l'immédiat) vont présenter ce que l'on appelle une sémantique d'entié.
Seulement, imagine que ton voisin achète une voiture qui ressemble exactement à la tienne, car
elle est de même marque
elle est de même modèle
elle a été construite le même jour du même mois de la même année
elle a la même motorisation
et à la limite, elle pourrait même avoir exactement le même nombre de kilomètres au compteur.
Il serait ballot que les flics t'envoient une contravention pour exces de vitesse en croyant l'envoyer à ton voisin qu'ils viennent de flâcher à 240 sur le périph, n'est-ce pas?
Il faut donc avoir "un moyen quelconque" de faire la différence entre ta voiture et celle de ton voisin.
Dans le cas présent, ce moyen est normalement fourni par le numéro d'immatriculation (et, accessoirement, par le numéro de chassis ). Les deux voitures peuvent être totalement identiques sur tous les autres points, il y aura toujours "un truc" (le numéro d'immatriculation) qui sera différent
De même, il serait tout aussi ballot que, voulant faire le plein de ta voiture, tu n'en vienne à faire le plein ... d'une copie de ta voiture (que tu n'utilises forcément pas), "simplement" parce que tu te serais trompé d'instance au moment de faire le plein.
Car c'est la copie dont le réservoir serait rempli, alors que le réservoir de l'original (l'instance que tu utilises normalement) resterait quant à lui presque vide, et tu finirais donc par tomber en panne d'essence (ce qui est vraiment la panne la plus idiote qui puisse t'arriver )
On se rend donc compte que l'on ne veut surtout pas risquer de se retrouver, à un instant T particulier de l'exécution du programme, avec deux instances présentant exactement les mêmes valeur (y compris le numéro d'immatriculation, dans le cas présent) à des endroits différents en mémoire, car c'est une situation qui provoquera systématiquement des catastrophes.
Si bien que les classes qui ont sémantique d'entité
ne peuvent surtout pas être copiées
ne peuvent surtout pas être assignées
n'ont aucun intérêt à être comparées (ne serait-ce que par égalité) dans leur "globalité" (même si on peut décider de comparer certaines valeurs, comme la marque, le modèle, la date de fabrication, le kilométrage ou même le numéro d'immatriculation)
sont globalement susceptibles d'être mutables (ce n'est malgré tout pas parce que tu ajoutes 100 000 km au compteur que tu te trouves avec une voiture différente :D)
sont de très bons candidats à intervenir dans une hiérarchie de classes (que ce soit en tant que "classe de base" ou que "classe dérivée, quand ce n'est pas les deux en même temps)
Hé bien, dans l'idéal, une classe de base imposera systématiquement la sémantique d'entité (comprends: ad minima l'impossibilité de copie et d'assignation) de manière à ce que toutes les classes qui pourraient en hériter (de manière directe ou indirecte) puisse être "frappées" des mêmes restrictions.
Depuis C++11, il est possible d'indiquer explicitement que le constructeur par copie et que l'opérateur d'affectation n'ont pas de raison d'exister sous une forme qui ressemble à
class Vehicule{
public:
/* le constructeur de copie que l'on refuse */
Vehicule(Vehicule const &) = delete;
/* l'opérateur d'affectation que l'on refuse de même */
Vehicule & operator = (Vehicule const &) = delete;
/* le destructeur que l'on rend virtuel pour permettre
* la destruction de n'importe quel "type de véhicule"
* sans avoir besoin d'en connaitre le type réel
* on le défini comme ayant un "comportement par défaut
* si on peut se permettre d'agir de la sorte
*/
virtual ~Vehicule() = defaut;
/* ... */
}
/* Et les classe qui en dérivent
class Voiture : public Vehicule{
/* ... */
};
class Moto : public Vehicule{
/* ... */
};
class Camion : public Vehicule{
/* ... */
};
class CharAvoile : public Vehicule{
/* ... */
};
Avant C++11, nous aurions du déclarer (sans les implémenter) le constructeur de copie et l'opérateur d'affectation dans l'accessibilité privée, mais cette manière de faire laissait quelques problèmes irrésolus
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
1) pour le using namespace , oui quand je code des trucs à la con, juste pour faire des tests, je mets tout le temps cette directive les "std::" partout ca va bien deux secondes mais par contre tu valides "using std::cout" par exemple ou pas non plus ?
En pratique, pour un entretien d'embauche... osef. Tu pourras utiliser using namespace et tomber sur une personne qui préfère std::. Ou l'inverse.
Et c'est osef, parce que tu ne décideras pas des guidelines des projets quand tu rejoindras l'équipe, mais tu respecteras les guidelines existantes.
Ce qui compte au final pour l'entretien, c'est pas tes préférences personnelles, mais de pouvoir justifier les avantages et défauts de chaque syntaxes.
Et ca sera pareil pour la majorité des questions que tu as posé en fait. Tu pourras tout a fait bosser sur des codes avec des héritages multiples, des classes de base sans destructeur virtuel, des variables membres protected, des classes a sémantiques de valeurs dans un héritage, 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.
Recueil de code C et C++ http://fvirtman.free.fr/recueil/index.html
Discord NaN. Mon site.