Ok, c'est vraiment difficile à avouer, mais j'ai une forte tentation en ce moment d'hériter std::vector
.
J'ai besoin d'environ 10 algorithmes personnalisés pour le vecteur et je veux qu'ils soient directement membres du vecteur. Mais naturellement je veux aussi avoir le reste de std::vector
l'interface de. Eh bien, ma première idée, en tant que citoyen respectueux des lois, était d'avoir un std::vector
membre en MyVector
classe. Mais alors je devrais réorganiser manuellement toute l'interface de std :: vector. Trop à taper. Ensuite, j'ai pensé à l'héritage privé, de sorte qu'au lieu de réapprovisionner les méthodes, j'écrirais un tas de using std::vector::member
's dans la section publique. C'est trop ennuyeux en fait.
Et me voici, je pense vraiment que je peux simplement hériter publiquement de std::vector
, mais fournissez un avertissement dans la documentation que cette classe ne doit pas être utilisée de manière polymorphe. Je pense que la plupart des développeurs sont suffisamment compétents pour comprendre que cela ne devrait de toute façon pas être utilisé de manière polymorphe.
Ma décision est-elle absolument injustifiable? Si oui, pourquoi? Pouvez-vous fournir une alternative qui aurait les membres supplémentaires réellement membres mais n'impliquerait pas de retaper toute l'interface du vecteur? J'en doute, mais si vous le pouvez, je serai juste heureux.
Aussi, mis à part le fait qu'un idiot peut écrire quelque chose comme
std::vector<int>* p = new MyVector
y a-t-il un autre danger réaliste à utiliser MyVector? En disant réaliste, je rejette des choses comme imaginer une fonction qui prend un pointeur vers le vecteur ...
Eh bien, j'ai exposé mon cas. J'ai pêché. Maintenant c'est à vous de me pardonner ou pas :)
std::vector
est assez énorme, et quand C ++ 1x arrive, il va considérablement s'étendre. C'est beaucoup à taper et plus à développer dans quelques années. Je pense que c'est une bonne raison d'envisager l'héritage plutôt que le confinement - si l'on part du principe que ces fonctions devraient être des membres (ce dont je doute). La règle pour ne pas dériver des conteneurs STL est qu'ils ne sont pas polymorphes. Si vous ne les utilisez pas de cette façon, cela ne s'applique pas.Réponses:
En fait, il n'y a rien de mal à l'héritage public de
std::vector
. Si vous en avez besoin, faites-le.Je suggérerais de ne le faire que si c'est vraiment nécessaire. Seulement si vous ne pouvez pas faire ce que vous voulez avec des fonctions gratuites (par exemple, vous devez conserver un certain état).
Le problème est qu'il
MyVector
s'agit d'une nouvelle entité. Cela signifie qu'un nouveau développeur C ++ doit savoir ce que c'est avant de l'utiliser. Quelle est la différence entrestd::vector
etMyVector
? Lequel est préférable d'utiliser ici et là? Que faire si je dois passerstd::vector
àMyVector
? Puis-je simplement utiliserswap()
ou non?Ne produisez pas de nouvelles entités juste pour rendre quelque chose plus beau. Ces entités (en particulier, si communes) ne vont pas vivre dans le vide. Ils vivront dans un environnement mixte avec une entropie constamment accrue.
la source
MyVector
, puis essayez de les transmettre aux fonctions qui acceptentstd::vector&
oustd::vector*
. S'il y a une sorte d'affectation de copie impliquée en utilisant std :: vector * ou std :: vector &, nous avons des problèmes de découpage où les nouveaux membres de données deMyVector
ne seront pas copiés. La même chose serait vraie pour appeler swap via un pointeur / référence de base. J'ai tendance à penser que toute sorte de hiérarchie d'héritage qui risque de trancher des objets est mauvaise.std::vector
le destructeur n'est pasvirtual
, donc vous ne devriez jamais en hériterL'ensemble de la STL a été conçu de telle manière que les algorithmes et les conteneurs sont séparés .
Cela a conduit à un concept de différents types d'itérateurs: itérateurs const, itérateurs à accès aléatoire, etc.
Par conséquent, je vous recommande d'accepter cette convention et de concevoir vos algorithmes de manière à ce qu'ils ne se soucient pas du conteneur sur lequel ils travaillent - et ils n'auraient besoin que d'un type spécifique d'itérateur dont ils auraient besoin pour effectuer leur opérations.
Permettez-moi également de vous renvoyer à quelques bonnes remarques de Jeff Attwood .
la source
La principale raison pour ne pas hériter de
std::vector
publiquement est l'absence d'un destructeur virtuel qui vous empêche efficacement d'utiliser polymorphes des descendants. En particulier, vous pas autorisé àdelete
unstd::vector<T>*
qui en fait des points à un objet dérivé (même si la classe dérivée ajoute aucun membre), mais le compilateur en général ne peut pas vous mettre en garde à ce sujet.L'héritage privé est autorisé dans ces conditions. Je recommande donc d'utiliser l'héritage privé et de transmettre les méthodes requises du parent comme indiqué ci-dessous.
Vous devriez d'abord envisager de refactoriser vos algorithmes pour faire abstraction du type de conteneur sur lequel ils opèrent et les laisser sous forme de fonctions de modèle gratuites, comme l'ont souligné la majorité des répondants. Cela se fait généralement en faisant accepter à un algorithme une paire d'itérateurs au lieu d'un conteneur comme arguments.
la source
vector
Le stockage alloué de n'est pas le problème - après tout,vector
le destructeur de s serait appelé directement via un pointeur versvector
. C'est juste que la norme interditdelete
les objets de stockage gratuits via une expression de classe de base. La raison en est sûrement que le mécanisme de (dé) allocation peut essayer de déduire la taille du bloc de mémoire à libérer dedelete
l'opérande de, par exemple lorsqu'il existe plusieurs arènes d'allocation pour des objets de certaines tailles. Cette restriction ne s'applique pas, afaics, à la destruction normale d'objets avec une durée de stockage statique ou automatique.Si vous envisagez cela, vous avez clairement déjà tué les pédants de langue dans votre bureau. Avec eux à l'écart, pourquoi ne pas simplement faire
Cela évitera toutes les erreurs possibles qui pourraient résulter de la mise à jour accidentelle de votre classe MyVector, et vous pouvez toujours accéder à toutes les opérations vectorielles simplement en ajoutant un peu
.v
.la source
Qu'espérez-vous accomplir? Fournir juste quelques fonctionnalités?
La manière idiomatique de C ++ de faire ceci est juste d'écrire quelques fonctions libres qui implémentent la fonctionnalité. Il y a de fortes chances que vous n'ayez pas vraiment besoin d'un std :: vector, en particulier pour la fonctionnalité que vous implémentez, ce qui signifie que vous perdez en fait la réutilisation en essayant d'hériter de std :: vector.
Je vous conseillerais fortement de regarder la bibliothèque standard et les en-têtes, et de méditer sur leur fonctionnement.
la source
front
etback
aussi. :) (Considérez également l'exemple de freebegin
etend
en C ++ 0x et boost.)Je pense que très peu de règles devraient être suivies aveuglément 100% du temps. Il semble que vous y ayez beaucoup réfléchi et que vous soyez convaincu que c'est la voie à suivre. Donc - à moins que quelqu'un ne propose de bonnes raisons spécifiques de ne pas le faire - je pense que vous devriez poursuivre votre plan.
la source
Il n'y a aucune raison d'hériter à
std::vector
moins que l'on veuille faire une classe qui fonctionne différemmentstd::vector
, parce qu'elle gère à sa manière les détails cachés destd::vector
la définition de 's, ou à moins que l'on ait des raisons idéologiques d'utiliser les objets d'une telle classe à la place destd::vector
ceux de. Cependant, les créateurs du standard sur C ++ n'ont fournistd::vector
aucune interface (sous forme de membres protégés) dont une telle classe héritée pourrait tirer parti pour améliorer le vecteur d'une manière spécifique. En effet, ils n'avaient aucun moyen de penser à un aspect spécifique qui pourrait nécessiter une extension ou une mise en œuvre supplémentaire affinée, ils n'avaient donc pas besoin de penser à fournir une telle interface à quelque fin que ce soit.Les raisons de la deuxième option ne peuvent être qu'idéologiques, car les
std::vector
s ne sont pas polymorphes, et sinon, il n'y a aucune différence si vous exposezstd::vector
l'interface publique de via l'héritage public ou via l'appartenance publique. (Supposons que vous ayez besoin de conserver un état dans votre objet afin que vous ne puissiez pas vous en sortir avec des fonctions gratuites). Sur une note moins saine et du point de vue idéologique, il apparaît que lesstd::vector
s sont une sorte d '«idée simple», donc toute complexité sous forme d'objets de différentes classes possibles à leur place ne sert à rien idéologiquement.la source
En termes pratiques: si vous n'avez aucun membre de données dans votre classe dérivée, vous n'avez aucun problème, pas même en utilisation polymorphe. Vous n'avez besoin d'un destructeur virtuel que si les tailles de la classe de base et de la classe dérivée sont différentes et / ou si vous avez des fonctions virtuelles (ce qui signifie une v-table).
MAIS en théorie: De [expr.delete] dans le C ++ 0x FCD: Dans la première alternative (supprimer un objet), si le type statique de l'objet à supprimer est différent de son type dynamique, le type statique doit être un la classe de base du type dynamique de l'objet à supprimer et le type statique doivent avoir un destructeur virtuel ou le comportement n'est pas défini.
Mais vous pouvez dériver en privé de std :: vector sans problème. J'ai utilisé le modèle suivant:
la source
[expr.delete]
dans le C ++ 0x FCD: <quote> Dans la première alternative (supprimer l'objet), si le type statique de l'objet à supprimer est différent de son type dynamique, le type statique doit être une classe de base du type dynamique de l'objet à supprimer et le type statique doit avoir un destructeur virtuel ou le comportement est indéfini. </quote>Si vous suivez un bon style C ++, l'absence de fonction virtuelle n'est pas le problème, mais le découpage (voir https://stackoverflow.com/a/14461532/877329 )
Pourquoi l'absence de fonctions virtuelles n'est-elle pas le problème? Parce qu'une fonction ne doit pas essayer vers
delete
un pointeur qu'elle reçoit, puisqu'elle n'en a pas la propriété. Par conséquent, si vous suivez des politiques de propriété strictes, les destructeurs virtuels ne devraient pas être nécessaires. Par exemple, c'est toujours faux (avec ou sans destructeur virtuel):En revanche, cela fonctionnera toujours (avec ou sans destructeur virtuel):
Si l'objet est créé par une fabrique, la fabrique doit également renvoyer un pointeur vers un suppresseur de travail, qui doit être utilisé à la place de
delete
, car la fabrique peut utiliser son propre tas. L'appelant peut l'obtenir sous la forme d'unshare_ptr
ouunique_ptr
. Bref, ne pasdelete
tout ce que vous n'avez pas directement à partirnew
.la source
Oui, c'est sûr tant que vous faites attention à ne pas faire les choses qui ne sont pas sûres ... Je ne pense pas avoir jamais vu qui que ce soit utiliser un vecteur avec du nouveau, donc en pratique, tout ira probablement bien. Cependant, ce n'est pas l'idiome commun en c ++ ...
Êtes-vous en mesure de donner plus d'informations sur ce que sont les algorithmes?
Parfois, vous finissez par emprunter une route avec un design et ne pouvez pas voir les autres chemins que vous auriez pu emprunter - le fait que vous prétendez avoir besoin de vectoriser avec 10 nouveaux algorithmes sonne l'alarme pour moi - y a-t-il vraiment 10 à usage général algorithmes qu'un vecteur peut implémenter, ou essayez-vous de créer un objet à la fois un vecteur à usage général ET qui contient des fonctions spécifiques à une application?
Je ne dis certainement pas que vous ne devriez pas faire cela, c'est juste qu'avec les informations que vous avez données, les sonnettes d'alarme sonnent ce qui me fait penser que peut-être que quelque chose ne va pas avec vos abstractions et qu'il y a un meilleur moyen de réaliser ce que vous vouloir.
la source
J'ai également hérité de
std::vector
récemment, et je l'ai trouvé très utile et jusqu'à présent, je n'ai rencontré aucun problème avec.Ma classe est une classe de matrice clairsemée, ce qui signifie que j'ai besoin de stocker mes éléments de matrice quelque part, à savoir dans un fichier
std::vector
. Ma raison d'hériter était que j'étais un peu trop paresseux pour écrire des interfaces pour toutes les méthodes et que j'interface également la classe avec Python via SWIG, où il existe déjà un bon code d'interface pourstd::vector
. J'ai trouvé beaucoup plus facile d'étendre ce code d'interface à ma classe plutôt que d'en écrire un nouveau à partir de zéro.Le seul problème que je peux voir avec l'approche n'est pas tant avec le destructor non virtuel, mais d'autres méthodes, que je voudrais, comme la surcharge
push_back()
,resize()
,insert()
etc. héritage privé pourrait en effet être une bonne option.Merci!
la source
Ici, laissez-moi vous présenter 2 autres façons de faire ce que vous voulez. L'une est une autre façon d'envelopper
std::vector
, une autre est la façon d'hériter sans donner aux utilisateurs une chance de casser quoi que ce soit:std::vector
sans écrire beaucoup d'encapsuleurs de fonctions.std::vector
et éviter le problème dtor.la source
Cette question est garantie pour produire des perles à couper le souffle, mais en fait, il n'y a aucune raison défendable d'éviter, ou "de multiplier inutilement des entités" pour éviter, la dérivation d'un conteneur standard. L'expression la plus simple et la plus courte possible est la plus claire et la meilleure.
Vous devez faire preuve de toutes les précautions habituelles autour de tout type dérivé, mais il n'y a rien de spécial dans le cas d'une base du Standard. Remplacer une fonction membre de base pourrait être délicat, mais ce ne serait pas sage de le faire avec une base non virtuelle, donc il n'y a pas grand chose de spécial ici. Si vous deviez ajouter un membre de données, vous devrez vous soucier du découpage si le membre devait rester cohérent avec le contenu de la base, mais encore une fois, c'est la même chose pour n'importe quelle base.
L'endroit où j'ai trouvé dérivant d'un conteneur standard particulièrement utile est d'ajouter un seul constructeur qui effectue précisément l'initialisation nécessaire, sans risque de confusion ou de détournement par d'autres constructeurs. (Je vous regarde, constructeurs initialization_list!) Ensuite, vous pouvez utiliser librement l'objet résultant, tranché - le passer par référence à quelque chose qui attend la base, passer de celui-ci à une instance de la base, qu'as-tu. Il n'y a pas de cas extrêmes à se soucier, à moins que cela ne vous dérange de lier un argument de modèle à la classe dérivée.
Un endroit où cette technique sera immédiatement utile en C ++ 20 est la réservation. Où nous pourrions avoir écrit
On peut dire
et puis, même en tant que membres de la classe,
(selon les préférences) et pas besoin d'écrire un constructeur juste pour appeler reserve () sur eux.
(La raison qui
reserve_in
, techniquement, doit attendre C ++ 20 est que les normes antérieures n'exigent pas que la capacité d'un vecteur vide soit préservée à travers les mouvements. Ceci est reconnu comme un oubli, et on peut raisonnablement s'attendre à ce qu'il soit corrigé comme un défaut dans le temps pour 2020. Nous pouvons également nous attendre à ce que le correctif soit, effectivement, antidaté par rapport aux normes précédentes, car toutes les implémentations existantes préservent la capacité d'un mouvement à l'autre; les normes ne l'exigent tout simplement pas. le pistolet - réserver est presque toujours juste une optimisation de toute façon.)Certains diront que le cas de
reserve_in
est mieux servi par un modèle de fonction gratuit:Une telle alternative est certainement viable - et pourrait même, parfois, être infiniment plus rapide, grâce à * RVO. Mais le choix de la dérivation ou de la fonction libre doit être fait sur ses propres mérites, et non à partir d'une superstition sans fondement (hé!) Sur la dérivation de composants standard. Dans l'exemple d'utilisation ci-dessus, seul le deuxième formulaire fonctionnerait avec la fonction libre; bien qu'en dehors du contexte de la classe, il pourrait être écrit un peu plus concis:
la source