209 votes

Tu n'hériteras pas de std::vector

Ok, c'est vraiment difficile à avouer, mais j'ai une forte tentation en ce moment d'hériter de 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 que le reste des algorithmes du vecteur soient des membres du vecteur. std::vector de l'interface. Eh bien, ma première idée, en tant que citoyen respectueux de la loi, était d'avoir une std::vector membre en MyVector classe. Mais alors je devrais manuellement reprovider toute l'interface de std::vector. Trop de choses à taper. Ensuite, j'ai pensé à l'héritage privé, de sorte qu'au lieu de fournir à nouveau des méthodes, j'écrirais un groupe de using std::vector::member dans la section publique. C'est également fastidieux en fait.

Et voilà, je pense vraiment que je peux simplement hériter publiquement de std::vector mais prévoyez un avertissement dans la documentation indiquant 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 cette classe ne doit pas être utilisée de manière polymorphe de toute façon.

Ma décision est-elle absolument injustifiable ? Si oui, pourquoi ? Pouvez-vous proposer une alternative qui aurait les membres supplémentaires suivants en fait mais qui n'impliquerait pas de retaper toute l'interface du vecteur ? J'en doute, mais si vous le pouvez, je serai heureux.

En outre, outre le fait qu'un idiot peut écrire quelque chose comme

std::vector<int>* p  = new MyVector

Y a-t-il un autre réaliste péril dans l'utilisation de MyVector ? En disant réaliste, j'écarte des choses comme imaginer une fonction qui prend un pointeur sur le vecteur ...

Eh bien, j'ai exposé mon cas. J'ai péché. Maintenant c'est à vous de me pardonner ou pas :)

2 votes

Re : std::vector<int>* p = new MyVector ne fonctionne pas vraiment en premier lieu, car std::vector n'a pas de destructeur virtuel. Il y aura très certainement un comportement non défini dans un futur proche.

9 votes

Donc, en gros, vous demandez si vous pouvez violer une règle commune parce que vous êtes trop paresseux pour réimplémenter l'interface du conteneur ? Alors non, ce n'est pas le cas. Vous voyez, vous pouvez avoir le meilleur des deux mondes si vous avalez cette pilule amère et faites les choses correctement. Ne soyez pas ce type de personne. Écrivez du code robuste.

0 votes

Pourquoi le passage d'un pointeur à un vecteur dans une fonction paramétrique est-il irréaliste ?

165voto

Stas Points 3333

En fait, il n'y a rien de mal à ce que l'héritage public des std::vector . Si vous avez besoin de ceci, faites-le.

Je suggère de ne le faire que si c'est vraiment nécessaire. Seulement si vous ne pouvez pas faire ce que vous voulez avec les fonctions libres (par exemple, vous devez conserver un certain état).

Le problème est que MyVector est 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 entre std::vector y MyVector ? Laquelle est la meilleure à utiliser ici et là ? Que faire si je dois déplacer std::vector a MyVector ? Puis-je simplement utiliser swap() ou pas ?

Ne produisez pas de nouvelles entités juste pour donner une meilleure apparence à quelque chose. Ces entités (surtout si elles sont communes) ne vont pas vivre dans le vide. Elles vont vivre dans un environnement mixte où l'entropie augmente constamment.

1 votes

J'adore cette réponse. Elle m'a permis de mieux comprendre les choix de conception des classes C++.

9 votes

Mon seul contre-argument à cela est qu'il faut vraiment savoir ce que l'on fait pour le faire. Par exemple, ne pas introduire des membres de données supplémentaires dans MyVector et ensuite essayer de le passer dans des fonctions qui acceptent std::vector& o std::vector* . Si une quelconque affectation par copie est impliquée en utilisant std::vector* ou std::vector&, nous avons des problèmes de découpage en tranches où les nouvelles données membres de MyVector ne sera pas copié. La même chose serait vraie si l'on appelait swap par le biais d'un pointeur/référence de base. J'ai tendance à penser que tout type de hiérarchie d'héritage qui risque le découpage d'objets est mauvais.

18 votes

std::vector Le destructeur de l'utilisateur n'est pas virtual donc vous ne devriez jamais hériter d'elle

94voto

Kos Points 29125

L'ensemble de la STL a été conçu de manière à ce 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.

Je vous recommande donc d'accepter cette convention et concevez vos algorithmes de manière à ce qu'ils ne se soucient pas de savoir quel est le conteneur sur lequel ils travaillent. - et ils n'auraient besoin que d'un type spécifique d'itérateur pour effectuer leurs opérations.

Aussi, permettez-moi de vous rediriger vers Quelques bonnes remarques de Jeff Attwood .

69voto

Basilevs Points 4048

La principale raison pour ne pas hériter de std::vector publiquement est l'absence d'un destructeur virtuel qui vous empêche effectivement d'utiliser les descendants de manière polymorphe. En particulier, vous êtes pas autorisé a delete a std::vector<T>* qui pointe en fait vers un objet dérivé (même si la classe dérivée n'ajoute aucun membre), mais le compilateur ne peut généralement pas vous en avertir.

L'héritage privé est autorisé dans ces conditions. Je recommande donc d'utiliser l'héritage privé et de transférer les méthodes requises du parent comme indiqué ci-dessous.

class AdVector: private std::vector<double>
{
    typedef double T;
    typedef std::vector<double> vector;
public:
    using vector::push_back;
    using vector::operator[];
    using vector::begin;
    using vector::end;
    AdVector operator*(const AdVector & ) const;
    AdVector operator+(const AdVector & ) const;
    AdVector();
    virtual ~AdVector();
};

Vous devriez d'abord envisager de remanier vos algorithmes afin d'abstraire le type de conteneur sur lequel ils opèrent et de les laisser comme des fonctions à modèle libre, comme l'ont souligné la majorité des répondants. Cela se fait généralement en faisant en sorte qu'un algorithme accepte une paire d'itérateurs au lieu d'un conteneur comme arguments.

1 votes

IIUC, l'absence d'un destructeur virtuel n'est un problème que si la classe dérivée alloue des ressources qui doivent être libérées lors de la destruction. (Elles ne seraient pas libérées dans un cas d'utilisation polymorphe car un contexte prenant sans le savoir la propriété d'un objet dérivé via un pointeur vers la base n'appellerait le destructeur de la base qu'au moment opportun). Des problèmes similaires se posent avec d'autres fonctions membres surchargées, donc il faut s'assurer que les fonctions de base sont valides pour être appelées. Mais en l'absence de ressources supplémentaires, y a-t-il d'autres raisons ?

2 votes

vector Le stockage alloué à l'entreprise n'est pas le problème - après tout, vector Le destructeur de l'utilisateur sera appelé directement par un pointeur vers le fichier vector . C'est juste que la norme interdit delete ing objets de magasin gratuits par le biais d'une expression de classe de base. La raison en est sûrement que le mécanisme de (dé)répartition peut essayer de déduire la taille du morceau de mémoire à libérer à partir de l'expression de la classe de base. delete par exemple lorsqu'il existe plusieurs zones d'allocation pour des objets de certaines tailles. Cette restriction ne s'applique pas, a priori, à la destruction normale des objets à durée de stockage statique ou automatique.

0 votes

@DavisHerring Je pense que nous sommes d'accord sur ce point :-).

39voto

Crashworks Points 22920

Si vous envisagez de le faire, c'est que vous avez déjà tué les pédants de votre bureau. Avec eux hors du chemin, pourquoi ne pas simplement faire

struct MyVector
{
   std::vector<Thingy> v;  // public!
   void func1( ... ) ; // and so on
}

Cela évitera toutes les erreurs possibles qui pourraient résulter d'un upcasting accidentel de votre classe MyVector, et vous pourrez toujours accéder à toutes les opérations vectorielles en ajoutant simplement un petit fichier .v .

0 votes

Et l'exposition des conteneurs et des algorithmes ? Voir la réponse de Kos ci-dessus.

0 votes

19voto

Karl Knechtel Points 24349

Qu'espérez-vous accomplir ? Fournir simplement une fonctionnalité ?

La façon idiomatique C++ de faire cela est d'écrire des fonctions libres qui implémentent la fonctionnalité. Il y a de fortes chances que vous n'avez pas vraiment besoin d'un std::vector, spécifiquement pour la fonctionnalité que vous implémentez, ce qui signifie que vous perdez en réalité en réutilisabilité en essayant d'hériter de std::vector.

Je vous conseille vivement de regarder la bibliothèque standard et les en-têtes, et de méditer sur leur fonctionnement.

1 votes

Oh, s'il vous plaît, je le sais, mais dans ce cas particulier, il serait TRÈS pratique que la nouvelle fonctionnalité soit celle des membres.

7 votes

Je ne suis pas convaincu. Pourriez-vous mettre à jour avec une partie du code proposé pour expliquer pourquoi ?

2 votes

Par exemple, à part back() et front(), je veux aussi fournir middle(), et je ne veux pas que cela ressemble à middle(v), mais à v.middle().

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X