C++: l'héritage de std::map
Je veux hériter de std::map
, mais pour autant que je sais std::map
n'a pas de destructeur virtuel.
Est-il donc possible de faire appel à std::map
's destructeur de manière explicite dans mon destructeur pour assurer le bon objet de la destruction?
Vous devez vous connecter pour publier un commentaire.
Le destructeur est appelé, même si ce n'est pas virtuel, mais ce n'est pas la question.
Vous obtenez un comportement indéfini si vous tentez de supprimer un objet de type par l'intermédiaire d'un pointeur vers un
std::map
.Utiliser la composition à la place de l'héritage,
std
les conteneurs ne sont pas destinés à être héréditaire, et vous ne devriez pas.Je suis en supposant que vous souhaitez étendre les fonctionnalités de
std::map
(dites que vous voulez trouver la valeur minimale), dans ce cas, vous avez deux beaucoup mieux, et juridique, options:1) Comme l'a suggéré, vous pouvez utiliser la composition à la place:
2) fonctions Gratuites:
using attribute.insert;
pourrait fonctionner! D'autre part, il est assez rare que vous avez réellement besoin de toutes les méthodes, et l'emballage donne la possibilité de donner un nom et de prendre de plus haut niveau types 🙂private
héritage pouris-implemented-by
, etpublic
héritage pouris-a
.using Base::method;
Il y a un malentendu: l'héritage -en dehors de la notion de pur de la programmation orientée objet, C++ n'est pas - n'est rien de plus qu'une "composition avec un haut membre, avec une décroissance de la capacité".
L'absence de fonctions virtuelles (et le destructeur n'est pas spécial, en ce sens) rend votre objet n'est pas polymorphe, mais si ce que vous faites est tout simplement "le réutiliser comportement et d'exposer l'interface native" de l'héritage n'exactement ce que vous avez demandé.
Destructeurs n'ont pas besoin d'être explicitement appelé les uns des autres, depuis leur appel est toujours enchaîné par la spécification.
sortie
Faire Un intégré dans B avec
qui produira exactement le même.
"Ne pas tirer si le destructeur n'est pas du virtuel" n'est pas une C++ obligatoire conséquence, mais seulement communément acceptée pas écrit (il n'y a rien dans la spec à ce sujet: en dehors d'une UB appelant supprimer sur une base) de la règle qui se pose avant le C++99, lors de la programmation orientée objet par héritage dynamique et des fonctions virtuelles est le seul paradigme de programmation C++ pris en charge.
Bien sûr, beaucoup de programmeurs à travers le monde fait leurs os avec ce genre d'école (les mêmes qui enseignent iostreams comme primitives, se déplace ensuite à la matrice et des pointeurs, et sur la dernière leçon de l'enseignant dit: "oh ... il y a des améliorations est aussi la STL qui a vecteur, la chaîne et autres fonctions avancées") et aujourd'hui, même si le C++ becamed multiparadigm, insistez toujours avec cette pure OOP règle.
Dans mon exemple A::~A() n'est pas virtuel exactement comme Un::bonjour. Ça veut dire quoi?
Simple: pour la même raison, l'appel de
A::hello
n'entraînera pas, en l'appelantB::hello
, appelantA::~A()
(par supprimer) n'entraînera pas deB::~B()
. Si vous pouvez accepter -en vous le style de programmation- la première affirmation, il n'y a aucune raison que vous ne pouvez pas accepter le deuxième. Dans mon exemple il n'y a pas deA* p = new B
qui recevradelete p
depuis Un::~A n'est pas virtuel et je sais ce que cela signifie.Exactement la même raison qui ne sera pas effectuer, en utilisant le deuxième exemple pour B,
A* p = &((new B)->a);
avec undelete p;
, bien que ce deuxième cas, parfaitement double avec la première, ne semble pas intéressant toute personne, pour aucune raison apparente.Le seul problème est "entretien", dans le sens que si ton code est affiché par un POO - programmeur va la lui refuser, non pas parce qu'il est mauvais en soi, mais parce qu'il a été dit de le faire.
En fait, le "ne pas tirer si le destructeur n'est pas virtuel" est parce que la plupart des programmeurs beleave qu'il y a trop de programmeurs qui ne savent pas qu'ils ne peuvent pas appeler delete sur un pointeur sur une base. (Désolé si ce n'est pas poli, mais après 30 ans d'expérience en programmation, je ne vois aucune autre raison!)
Mais votre question est différente:
L'appel de B::~B() (par suppression ou par la portée de fin) entraînera toujours Une::~A() depuis Un (qu'il soit intégré ou hérité) est en tout cas une partie de B.
Suivantes Luchian commentaires: le comportement non défini fait allusion ci-dessus une de ses commentaires est liée à une délétion sur un pointeur-vers-un-objet-base avec pas de destructeur virtuel.
En fonction de la programmation orientée objet à l'école, cela se traduit par la règle "ne pas établie si pas de destructeur virtuel existe pas".
Ce que je fais remarquer, ici, est que les motifs de cette école repose sur le fait que chaque OOP orientée objet doit être polymorphe et tout est polymorphe doit être adressable par pointeur vers une base, pour permettre à l'objet de la substitution.
En rendant ceux affirmation, que l'école tente de faire annuler l'intersection entre la dérivée et non remplaçables, de sorte qu'une pure OOP programme ne sera pas une expérience que l'UB.
Ma position, tout simplement, admet que le C++ n'est pas juste de la programmation orientée objet, et non pas tous les objets C++ doivent ÊTRE OOP orientés par défaut, et, en admettant la POO n'est pas toujours un besoin indispensable, admet également que le C++ héritage n'est pas toujours nécessairement l'entretien de la programmation orientée objet de la substitution.
std::map n'est PAS polymorphe de sorte qu'il n'est PAS remplaçable.
MyMap est le même: PAS polymorphes et n'est PAS remplaçable.
Il suffit de réutiliser des std::map et exposer la même std::map interface. Et l'héritage est juste le moyen d'éviter une longue passe-partout de réécrite fonctions qui appelle simplement la réutilisés ceux.
MyMap n'aura pas de virtuel dtor comme std::map qui ne l'est pas. Et ce, pour moi, est suffisant pour dire un programmeur C++ que ce ne sont pas des objets polymorphes et qui ne doivent pas être utilisés l'un à la place de l'autre.
Je dois admettre que cette position n'est pas aujourd'hui partagée par la plupart de la C++ experts. Mais je pense (mon avis personnel) c'est uniquement en raison de leur histoire, qui se rapportent à la programmation orientée objet comme un dogme pour servir, non pas à cause d'un C++ besoin.
Pour moi C++ n'est pas un pur langage de programmation orientée objet et ne doit pas nécessairement suivre le paradigme de la programmation orientée objet, dans un contexte où la POO n'est pas suivie ou requis.
std::map*
qui en fait des points deMyMap
. Et si vous la supprimez, tout peut arriver, y compris un accident.shared_ptr<std::map>
partout...Pourquoi ?
Il y a deux raisons traditionnelles d'hériter:
L'ancien n'a pas de sens ici comme
map
n'a pas devirtual
méthode de sorte que vous ne peut pas modifier son comportement en héritant; et le dernier est une perversion de l'utilisation de l'héritage qui ne fait que compliquer l'entretien à la fin.Sans une idée claire de votre utilisation prévue (manque de contexte de votre question), je suppose que ce que vous voulez vraiment est de fournir une carte de type conteneur, avec quelques bonus opérations. Il y a deux façons d'y parvenir:
std::map
, et de fournir de manière adéquate interfacestd::map
Ce dernier est plus simple, cependant, il est aussi plus ouvert: l'interface d'origine de
std::map
est encore large-ouvert; par conséquent, il est inapproprié pour limitant opérations.Le premier est plus lourd, sans aucun doute, mais il offre plus de possibilités.
C'est à vous de décider laquelle des deux approches est la plus adaptée.
@Matthieu M
vous avez dit
Quant à "l'ancienne":
La
clear()
fonction est virtuelle, et pour moi, il fait beaucoup de sens pour unstd::map<key,valueClass*>::clear()
d'être redéfinie dans une classe dérivée, avec un itérateur qui supprime tous les souligné les instances de la classe de valeur avant l'appel de la classe de baseclear()
pour empêcher les fuites de mémoire, et c'est un truc que j'ai effectivement utilisé. Quant à savoir pourquoi quelqu'un voudrait utiliser une carte de pointeurs vers des classes, bien polymorphisme et les références de ne pas être ré-assignable signifie que la ne peut pas être utilisé dans un conteneur STL. Vous pourriez plutôt suggérer l'utilisation d'un reference_wrapper ou pointeur intelligent comme unshared_ptr
(C++11 caractéristiques) mais quand vous écrivez une bibliothèque que vous voulez quelqu'un restreint à un C++98 compilateur pour être en mesure d'utiliser, ceux-là ne sont pas une option, sauf si vous allez mettre une nécessité d'avoir boost, qui peut également être indésirable. Et si vous voulez vraiment la carte pour avoir la propriété exclusive de son contenu, alors vous ne voulez pas être à l'aide de reference_wrapper ou la plupart des implémentations de pointeurs intelligents.Concernant le "dernier":
Si vous voulez une carte de pointeurs auto supprime rappelé à la mémoire, puis la réutilisation de "tous" les autres comportement de la carte et dominante claire fait beaucoup de sens pour moi, bien sûr, alors vous voudrez aussi de remplacer l'affectation/copier les constructeurs de cloner l'a souligné objets lorsque vous copiez la carte de sorte que vous n'avez pas le double de supprimer un relevé à l'instance de la
valueClass
.Mais c'est seulement requiert une très petite quantité de codage à mettre en œuvre.
J'ai également protégés
typedef std::map<key,valueClass*> baseClassMap;
que les 2 premières lignes de la déclaration de la classe dérivée de la carte, de sorte que ce que je peux appelerbaseClassMap::clear();
dans le substituéeclear()
fonction après l'itération de la boucle supprime toutes les instances devalueClass*
contenues dans le calcul de la carte, qui facilitent la maintenance dans le cas où le type devalueClass*
ne change jamais.Le point est, alors qu'il peut avoir limité l'applicabilité dans les bonnes pratiques de codage, je ne pense pas qu'il est juste de dire que ce n'est JAMAIS une bonne idée de descendre de la carte. Mais peut-être que vous avez une meilleure idée que je n'ai pas pensé sur la façon d'obtenir le même gestion automatique de la mémoire de l'effet sans l'ajout d'une quantité importante de supplémentaires de code source (par exemple, l'agrégation d'un
std::map
).