J'ai initialement demandé cette question sur Stackoverflow, mais j'étais dirigé ici, et je pense que mon problème est peut-être aussi conceptuel que technique, alors voilà.
Si vous définissez une hiérarchie de classe abstraite en C++, puis créez des sous-classes concrètes avec des implémentations, vous pourriez vous retrouver avec des classes abstraites quelque chose comme ça, par exemple:
A
/ \
B1 B2
De sorte que les classes concrètes héritent alors comme:
B1 B2 B1 B2
| | | |
C1 C2 D1 D2
Et tout cela est bien et dandy lorsque Cn
et Dn
implémente les interfaces de Bn
ou où dites C1
Et C2
Implémentez l'interface A
différemment.
Cependant, si je veux avoir une fonctionnalité partagée dans C1
Et C2
, Qui vient de l'interface A
, où est-ce que je le mettez-le?
Il ne peut pas aller dans A
, les deux parce que A
est abstrait et parce que Dn
devrait pas le hériter.
Il semble qu'il y ait un notionnel A_for_C
Mise en œuvre, mais cela appartient-il dans une autre classe ancêtre? Ou dans une classe de soeur composée?
_____A_____ _____A_____
/ | \ / | \
B1 A_for_C B2 vs B1 B2 A_for_C
|_____/ \____ | | |
C1 C2 C1 C2
(C1 and C2 then each have an A_for_C and delegate)
Le premier semble conceptuellement précis, mais nécessite virtual
héritage, tandis que la seconde requiert une délégation. Donc, tous deux imposent une performance touchée malgré la réelle ambiguïté.
Lire autour du web, sur ce site Web Je trouve que cela dit
Certaines personnes croient que le but de l'héritage est la réutilisation du code. En C++, c'est faux. Indiqué clairement, "l'héritage n'est pas pour la réutilisation du code".
Comment alors la mise en œuvre devrait-elle être partagée?
Principales pensées
J'ai trouvé une discussion pertinente dans ces questions:
Je pense que la réponse de Utnapismis ci-dessous est beaucoup plus collective et au point que celles-ci et m'a aidé mentalement coupé à travers ce que discuter de ces autres questions/réponses.
L'héritage consiste à accepter de remplir un contrat. Plusieurs héritages conviennent bien si la sous-classe garantit vraiment les contrats parents.
La mise en œuvre, cependant, n'est que vraiment la préoccupation de l'objet final. Oui, il peut être pratique d'hériter de la mise en œuvre, mais c'est en réalité orthogonal à l'interface, et il existe diverses techniques pour tirer dans une mise en œuvre autre que l'approche par défaut en V-Table, y compris:
(Qui sont, je pense, équivalent que celui-ci est compilé et que l'un est d'exécution.)
Votre question comporte deux aspects:
§1. Quel est le héritage C++ pour (si pas pour la réutilisation du code)?
La réponse la plus simple à donner ici est la "mise en œuvre d'un contrat" (voir également principe de substitution de Liskov et modèle de conception de façade ).
§2. Comment alors la mise en œuvre devrait-elle être partagée?
Envisager:
A_for_C
n'a pas besoin d'hériter de A
.Je pense que la plupart des problèmes "héritage" sont en son nom. Surtout lorsqu'il s'agit de C++, que comme une langue multiparadigm ne doit pas nécessairement obéir au OOP paradigmes, modèle et terminologie.
Si vous avez été mis de côté à partir de toute terminologie spécifique au paradigme, C++ offre deux méthodes de "composition" (avec une mémoire en anglais ordinaire):
struct A { B m; } a; a.m.fn(); // B::fn
)struct A: B {} a; a.fn(); // B::fn
)(Je n'ai pas utilisé de "composition" et "héritage" juste pour ne pas "distraire" dans OOP terminologie)
La seconde produise la même structure de structure de données la même structure de la structure de données que le premier, il fait simplement le nom m
Nom implicite et permet de se comporter implicitement comme B. Si nous ne faisons pas de fonction virtuelle à entrer en jeu, c'est juste un moyen d'importer le comportement défini en B dans un sans la nécessité d'écrire plus de code dans une personne elle-même.
Il n'y a aucune intention d'appliquer un principe de substitution, ici. Il n'y a pas OOP concept dans ceci. Ce n'est pas ce que OOP Appel scolaire "Héritage". Il a juste d'ailleurs avoir le même nom donné par le =OOP School et la spécification C++ Laguage.
std::true_type
Hérite de std::integal_constant
. Aucun d'entre eux n'a de méthodes virtuelles (incluse le destructeur). De A OOP CURIST Ceci est un blasphème, mais nerveux, cela fait partie de la bibliothèque standard C++.
Lorsque vous effectuez une fonction virtuelle pour entrer en jeu, vous acquérez la capacité de "remplacer" un comportement avec un autre (peut être déclaré comme non défini). Cela rend les classes C++ à devenir très similaires à ce que OOP appelle l'école "objet" et implicite incorporant ce qu'ils appellent "héritage".
Mais C++ offre un autre "mécanisme de substitution" le plus souvent ignoré (ainsi que des fonctions virtuelles): la domination. Il est souvent ignoré car cela signifie moins que des langages de héritage unique, mais jouent dans plusieurs héritages.
Considère ceci:
struct Inteface_A
{
virtual fnA() = 0;
virtual ~Inteface_A() {};
};
struct Implementation_A_comon
{
virtual fnA() { cout << "common implementation of IA" << endl; }
virtual ~Implementation_A_comon() {}
};
struct Implementation_A_special
{
virtual fnA() { cout << "special implementation of IA" << endl; }
virtual ~Implementation_A_special() {}
};
class Actual_object:
public Inteface_A,
protected Implementation_A_comon,
public Interface_B, //not declared here, but may be in another header
protected Implementation_B_common // not declared here, may be in yet another header
{
};
class Another_object:
public Inteface_A,
protected Implementation_A_special
{
};
Ici tous les deux réal_object et autre_Object peut être un substitut valide de Inteface_A
, Et comme fn
est Pure (Résumé, dans d'autres littératures) Un appel à ia->fn()
Donne l'appel à retrouver dans la seule méthode FN valide héritée de l'objet dérivé. Et c'est celui fourni par le " mise en œuvre" hérité de la classe (en C++ pure sens). C'est dominace
.
Vous pouvez facilement imaginer cela étendu avec un certain nombre d'interfaces différentes et un certain nombre d'une "mise en oeuvre partielle différente", héritante d'une autre de manière variable, se complétant de fournir différents comportements à importer dans un objet final.
Est-ce orthodoxe OOP? Absolument pas. Est-ce "POOP valide"? Respect de l'interface Oui, respect des implémentations NO (ou au moins, pas correctement). Est-ce valide C++: Oui. Et aussi une bonne réutilisation de code (pas de mise en œuvre pour réécrire de nombreuses fois dans chaque objet qui a une même interface à mettre en œuvre) à l'aide de polymorphisme de type à temps d'exécution, séjourner à partir de modèles et de polymorphes statiques, pas adéquat où différents types d'objets doivent Coexistez dans un même contexte d'exécution: CRTPS IA<A>
et IA<B>
sont tous deux nommés IA
, ont la même interface, mais à partir d'une perspective d'exécution ne sont pas liées. Vous ne pouvez pas avoir de std::vector<something>
Pouvant les renvoyer à la fois.
Quelque chose du pur OOP School n'accepte tout simplement pas simplement parce que ... ils font confiance à leurs propres programmeurs dans la compréhension de tout cela. Mais pour moi ... c'est leur faute, pas des mi.