web-dev-qa-db-fra.com

Tu ne hériteras pas de std :: vector

Ok, c’est vraiment difficile à avouer, mais j’ai une forte tentation pour le 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 avoir le reste de l'interface de std::vector. Eh bien, ma première idée, en tant que citoyen respectueux de la loi, était d’avoir un membre std::vector dans la classe MyVector. Mais ensuite, je devrais réapprovisionner manuellement toutes les interfaces de std :: vector. Trop de choses à taper. Ensuite, j'ai pensé à l'héritage privé, de sorte qu'au lieu de reprendre les méthodes, j'écrive un groupe de using std::vector::member dans la section publique. C'est fastidieux aussi réellement. 

Et me voilà, je pense vraiment que je peux simplement hériter publiquement de std::vector, mais je préviens 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 en fait membres mais n'impliquerait pas de ressaisir toute l'interface de vector? J'en doute, mais si vous le pouvez, je serai juste heureux.

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

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

y at-il d’autresréalistespérils dans l’utilisation de MyVector? En disant réaliste, je rejette des choses comme imaginer une fonction qui prend un pointeur sur un vecteur ...

Eh bien, j'ai énoncé mon cas. J'ai pêché. Maintenant, à toi de me pardonner ou non :)

166
Armen Tsirunyan

En réalité, il n'y a rien de mal avec 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 libres (par exemple, vous devriez garder un é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 et MyVector? Lequel est préférable d'utiliser ici et là? Que se passe-t-il si je dois déplacer std::vector vers MyVector? Puis-je simplement utiliser swap() ou non?

Ne produisez pas de nouvelles entités uniquement pour améliorer l'apparence. Ces entités (surtout communes) ne vont pas vivre dans le vide. Ils vivront dans un environnement mixte avec une entropie constamment accrue.

144
Stas

L'ensemble de la STL a été conçu de telle sorte 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 concevez vos algorithmes de telle sorte qu’ils ne se soucient pas du conteneur sur lequel ils travaillent} - et qu’ils n’auraient besoin que d’un type spécifique d’itérateur auquel 'aurais besoin d'effectuer leurs opérations.

Aussi, laissez-moi vous rediriger vers quelques bonnes remarques de Jeff Attwood .

84
Kos

La principale raison de ne pas hériter de std::vector publiquement est l'absence d'un destructeur virtuel qui vous empêche efficacement d'utiliser des polymorphes de descendants. En particulier, vous êtes pas autorisé à delete un std::vector<T>* qui pointe en fait sur 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 vous recommande donc d'utiliser l'héritage privé et de transférer les méthodes requises à partir 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 refactoriser vos algorithmes afin d’abréger le type de conteneur sur lequel ils opèrent et de les laisser comme des fonctions libres basées sur des modèles, comme l’a 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 de conteneur comme arguments.

46
Basilevs

Si vous envisagez cela, vous avez clairement déjà tué les pédants de langue dans votre bureau. Avec eux, pourquoi ne pas simplement faire

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

Cela évitera toutes les erreurs possibles dues à la surchauffe accidentelle de votre classe MyVector et vous pourrez toujours accéder à toutes les opérations vectorielles en ajoutant simplement un petit .v.

34
Crashworks

Qu'espérez-vous accomplir? Juste en fournissant des fonctionnalités?

Pour ce faire, la méthode idiomatique C++ consiste simplement à écrire des fonctions libres qui implémentent les fonctionnalités. Les chances sont vous n'avez pas vraiment besoin d'un std :: vector en particulier pour la fonctionnalité que vous implémentez, ce qui signifie que vous perdez la possibilité de réutilisation en essayant d'hériter de std :: vector.

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

18
Karl Knechtel

Je pense que très peu de règles devraient être suivies aveuglément 100% du temps. On dirait que vous y avez beaucoup réfléchi et que vous êtes convaincu que c'est la voie à suivre. Donc - à moins que quelqu'un ne présente de bonnes spécifiques raisons de ne pas le faire - je pense que vous devriez aller de l'avant avec votre plan.

11
NPE

Il n'y a aucune raison d'hériter de std::vector à moins de vouloir créer une classe qui fonctionne différemment de std::vector, car elle gère à sa manière les détails cachés de la définition de std::vector, ou à moins que des raisons idéologiques ne l'utilisent à sa place. de ceux de std::vector. Cependant, les créateurs de la norme sur C++ n’ont fourni à std::vector aucune interface (sous la forme de membres protégés) dont une classe héritée pourrait tirer parti afin d’améliorer le vecteur de 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 au point supplémentaire, donc ils n'avaient pas besoin de penser à fournir une telle interface à quelque fin que ce soit.

Les raisons de la deuxième option peuvent être uniquement idéologiques, parce que std::vectors ne sont pas polymorphes, et il n'y a aucune différence si vous exposez l'interface publique de std::vector via un héritage public ou un abonnement public. (Supposons que vous deviez conserver un état dans votre objet pour ne pas vous échapper avec des fonctions libres). Sur une note moins saine et du point de vue idéologique, il apparaît que les std::vectors sont une sorte de "simple idée", de sorte que toute complexité sous la forme d'objets de différentes classes possibles à leur place sur le plan idéologique est vaine.

6
Evgeniy

J'ai également hérité de std::vector récemment et je l'ai trouvé très utile. Jusqu'à présent, je n'ai rencontré aucun problème avec.

Ma classe est une classe matricielle clairsemée, ce qui signifie que je dois stocker mes éléments matriciels quelque part, à savoir dans un std::vector. Mon motif d'héritage était que j'étais un peu trop paresseux pour écrire des interfaces avec toutes les méthodes et que je connectais également la classe à Python via SWIG, où il existe déjà un bon code d'interface pour std::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 ne concerne pas tant le destructeur non virtuel, mais plutôt d'autres méthodes que j'aimerais surcharger, telles que Push_back(), resize(), insert(), etc. L'héritage privé pourrait en effet être une bonne option .

Merci!

2
Joel Andersson

Concrètement: si vous n’avez pas de données membres 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 (delete object), si le type statique de l'objet à supprimer est différent de son type dynamique, type 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 n'est pas défini.

Mais vous pouvez dériver en privé de std :: vector sans problème ..__ J'ai utilisé le motif suivant:

class PointVector : private std::vector<PointType>
{
    typedef std::vector<PointType> Vector;
    ...
    using Vector::at;
    using Vector::clear;
    using Vector::iterator;
    using Vector::const_iterator;
    using Vector::begin;
    using Vector::end;
    using Vector::cbegin;
    using Vector::cend;
    using Vector::crbegin;
    using Vector::crend;
    using Vector::empty;
    using Vector::size;
    using Vector::reserve;
    using Vector::operator[];
    using Vector::assign;
    using Vector::insert;
    using Vector::erase;
    using Vector::front;
    using Vector::back;
    using Vector::Push_back;
    using Vector::pop_back;
    using Vector::resize;
    ...
2
hmuelner

Si vous suivez un bon style C++, l’absence de fonction virtuelle n’est pas le problème mais slicing (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 devrait pas essayer de modifier delete aucun pointeur qu'elle reçoit, car elle n'en est pas propriétaire. Par conséquent, si vous suivez des règles de propriété strictes, les destructeurs virtuels ne devraient pas être nécessaires. Par exemple, cela est toujours faux (avec ou sans destructeur virtuel):

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    delete obj;
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj); //Will crash here. But caller does not know that
//  ...
    }

En revanche, cela fonctionnera toujours (avec ou sans destructeur virtuel):

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj);
//  The correct destructor *will* be called here.
    }

Si l'objet est créé par une fabrique, celle-ci doit également renvoyer un pointeur sur un deleter actif, qui doit être utilisé à la place de delete, car elle peut utiliser son propre segment de mémoire. L'appelant peut l'obtenir sous la forme d'un share_ptr ou d'un unique_ptr. En bref, ne delete rien que vous n’ayez obtenu directement de new.

2
user877329

Oui, c'est sûr tant que vous veillez à ne pas faire des choses qui ne sont pas sûres ... Je ne pense pas avoir jamais vu quelqu'un utiliser un vecteur avec un nouveau. Donc, dans la pratique, tout ira bien pour vous. Cependant, ce n'est pas l'idiome commun en c ++ ....

Pouvez-vous donner plus d’informations sur les algorithmes?

Parfois, vous vous retrouvez dans une voie avec un design et ensuite vous 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 - existe-t-il vraiment un objectif général? algorithmes qu'un vecteur peut implémenter, ou essayez-vous de créer un objet qui est à la fois un vecteur à usage général ET qui contient des fonctions spécifiques à une application?

Je ne suis certainement pas en train de dire que vous ne devriez pas faire cela, mais simplement qu'avec les informations que vous avez données, les cloches sonnent, ce qui me fait penser que quelque chose ne va pas dans vos abstractions et qu'il existe un meilleur moyen de réaliser ce que vous voulez. vouloir.

1
jcoder

Ici, laissez-moi vous présenter 2 autres façons de faire ce que vous voulez. L'une est une autre façon d'encapsuler std::vector, une autre est la manière d'hériter sans donner aux utilisateurs une chance de tout casser:

  1. Permettez-moi d’ajouter un autre moyen d’emballer std::vector sans écrire beaucoup d’emballages de fonctions.

#include <utility> // For std:: forward
struct Derived: protected std::vector<T> {
    // Anything...
    using underlying_t = std::vector<T>;

    auto* get_underlying() noexcept
    {
        return static_cast<underlying_t*>(this);
    }
    auto* get_underlying() const noexcept
    {
        return static_cast<underlying_t*>(this);
    }

    template <class Ret, class ...Args>
    auto apply_to_underlying_class(Ret (*underlying_t::member_f)(Args...), Args &&...args)
    {
        return (get_underlying()->*member_f)(std::forward<Args>(args)...);
    }
};
  1. Hériter de std :: span au lieu de std::vector et éviter le problème de dtor.
0
JiaHao Xu