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

189

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::vectorl'interface de. Eh bien, ma première idée, en tant que citoyen respectueux des lois, était d'avoir un std::vectormembre en MyVectorclasse. 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 :)

Armen Tsirunyan
la source
9
Donc, vous demandez en gros s'il est acceptable de violer une règle commune en vous basant sur le fait 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 le faites correctement. Ne sois pas ce type. Écrivez du code robuste.
Jim Brissom
7
Pourquoi ne pouvez-vous / ne voulez pas ajouter les fonctionnalités dont vous avez besoin avec des fonctions non membres? Pour moi, ce serait la chose la plus sûre à faire dans ce scénario.
Simone
11
L'interface de @Jim: std::vectorest 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.
sbi
9
Le vrai cœur de la question est dans la seule phrase: "Je veux qu'ils soient directement membres du vecteur". Rien d'autre dans la question n'a vraiment d'importance. Pourquoi vous voulez ceci? Quel est le problème avec la simple fourniture de cette fonctionnalité en tant que non-membres?
jalf
8
@JoshC: "Tu seras" a toujours été plus courant que "tu le feras", et c'est aussi la version trouvée dans la Bible King James (qui est généralement ce à quoi les gens font allusion lorsqu'ils écrivent "tu ne [...] "). Qu'est-ce qui vous amènerait à l'appeler une "faute d'orthographe"?
ruakh

Réponses:

155

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 MyVectors'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 entre std::vectoret MyVector? Lequel est préférable d'utiliser ici et là? Que faire si je dois passer std::vectorà MyVector? Puis-je simplement utiliser swap()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.

Stas
la source
7
Mon seul contre-argument à cela est qu'il faut vraiment savoir ce qu'il fait pour y parvenir. Par exemple, n'introduisez pas de membres de données supplémentaires dans MyVector, puis essayez de les transmettre aux fonctions qui acceptent std::vector&ou std::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 de MyVectorne 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.
stinky472
13
std::vectorle destructeur n'est pas virtual, donc vous ne devriez jamais en hériter
André Fratelli
2
J'ai créé une classe qui a hérité publiquement de std :: vector pour cette raison: j'avais un vieux code avec une classe vectorielle non-STL, et je voulais passer à STL. J'ai réimplémenté l'ancienne classe en tant que classe dérivée de std :: vector, me permettant de continuer à utiliser les anciens noms de fonction (par exemple, Count () plutôt que size ()) dans l'ancien code, tout en écrivant un nouveau code en utilisant le std :: vector les fonctions. Je n'ai ajouté aucun membre de données, donc le destructeur de std :: vector fonctionnait bien pour les objets créés sur le tas.
Graham Asher
3
@GrahamAsher Si vous supprimez un objet via un pointeur vers la base et que le destructeur n'est pas virtuel, votre programme présente un comportement indéfini. Un résultat possible d'un comportement non défini est "cela a bien fonctionné dans mes tests". Un autre est qu'il envoie par courrier électronique à votre grand-mère votre historique de navigation Web. Les deux sont conformes à la norme C ++. Le passage de l'un à l'autre avec des versions ponctuelles de compilateurs, d'OS ou de phase de la lune est également conforme.
Yakk - Adam Nevraumont
2
@GrahamAsher Non, chaque fois que vous supprimez un objet via un pointeur vers la base sans destructeur virtuel, c'est un comportement indéfini selon la norme. Je comprends ce que vous pensez qu'il se passe; vous vous trompez. "le destructeur de classe de base est appelé, et ça marche" est un symptôme possible (et le plus courant) de ce comportement indéfini, car c'est le code machine naïf que le compilateur génère habituellement. Cela ne le rend pas sûr ni une bonne idée à faire.
Yakk - Adam Nevraumont
92

L'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 .

Kos
la source
63

La principale raison pour ne pas hériter de std::vectorpubliquement est l'absence d'un destructeur virtuel qui vous empêche efficacement d'utiliser polymorphes des descendants. En particulier, vous pas autorisé à deleteun std::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.

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 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.

Basilevs
la source
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. (Ils ne seraient pas libérés dans un cas d'utilisation polymorphe car un contexte s'appropriant sans le savoir un objet dérivé via un pointeur vers la base n'appellerait le destructeur de base que lorsque le moment est venu.) être pris que ceux de base sont valides pour appeler. Mais en l'absence de ressources supplémentaires, y a-t-il d'autres raisons?
Peter - Réintègre Monica
2
vectorLe stockage alloué de n'est pas le problème - après tout, vectorle destructeur de s serait appelé directement via un pointeur vers vector. C'est juste que la norme interdit deleteles 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 de deletel'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.
Peter - Réintégrer Monica
@DavisHerring Je pense que nous sommes d'accord là-bas :-).
Peter - Réintègre Monica le
@DavisHerring Ah, je vois, vous vous référez à mon premier commentaire - il y avait un IIUC dans ce commentaire, et il s'est terminé par une question; J'ai vu plus tard qu'en effet c'est toujours interdit. (Basilevs avait fait une déclaration générale, "empêche effectivement", et je me suis demandé de quelle manière spécifique il empêche.) Alors oui, nous sommes d'accord: UB.
Peter - Réintègre Monica le
@Basilevs Cela a dû être par inadvertance. Fixé.
ThomasMcLeod
36

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

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

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.

Crashworks
la source
Et exposer des conteneurs et des algorithmes? Voir la réponse de Kos ci-dessus.
bruno nery le
19

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.

Karl Knechtel
la source
5
Je ne suis pas convaincu. Pourriez-vous mettre à jour une partie du code proposé pour expliquer pourquoi?
Karl Knechtel
6
@Armen: à part l'esthétique, y a-t-il de bonnes raisons?
snemarch
12
@Armen: Une meilleure esthétique, et une plus grande généricité, serait de fournir des fonctions gratuites frontet backaussi. :) (Considérez également l'exemple de free beginet enden C ++ 0x et boost.)
UncleBens
3
Je ne sais toujours pas ce qui ne va pas avec les fonctions gratuites. Si vous n'aimez pas «l'esthétique» de la STL, peut-être que C ++ n'est pas le bon endroit pour vous, esthétiquement. Et l'ajout de fonctions membres ne résoudra pas le problème, car de nombreux autres algorithmes sont toujours des fonctions gratuites.
Frank Osterfeld
17
Il est difficile de mettre en cache le résultat d'une opération lourde dans un algorithme externe. Supposons que vous deviez calculer une somme de tous les éléments du vecteur ou résoudre une équation polynomiale avec des éléments vectoriels comme coefficients. Ces opérations sont lourdes et la paresse leur serait utile. Mais vous ne pouvez pas l'introduire sans envelopper ou hériter du conteneur.
Basilevs
14

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.

NPE
la source
9
Votre première phrase est vraie 100% du temps. :)
Steve Fallows
5
Malheureusement, la deuxième phrase ne l'est pas. Il n'y a pas beaucoup réfléchi. La plupart de la question n'est pas pertinente. La seule partie qui montre sa motivation est "je veux qu'ils soient directement membres du vecteur". Je voudrais. Aucune raison pour laquelle cela est souhaitable. Ce qui donne l'impression qu'il n'y a pas du tout pensé .
jalf
7

Il n'y a aucune raison d'hériter à std::vectormoins que l'on veuille faire une classe qui fonctionne différemment std::vector, parce qu'elle gère à sa manière les détails cachés de std::vectorla définition de 's, ou à moins que l'on ait des raisons idéologiques d'utiliser les objets d'une telle classe à la place de std::vectorceux de. Cependant, les créateurs du standard sur C ++ n'ont fourni std::vectoraucune 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::vectors ne sont pas polymorphes, et sinon, il n'y a aucune différence si vous exposez std::vectorl'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 les std::vectors 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.

Evgeniy
la source
Très bonne réponse. Bienvenue à SO!
Armen Tsirunyan
4

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:

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;
    ...
hmuelner
la source
3
"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 nad / ou si vous avez des fonctions virtuelles (ce qui signifie une v-table)." Cette affirmation est pratiquement correcte, mais pas théoriquement
Armen Tsirunyan
2
oui, en principe c'est encore un comportement indéfini.
jalf
Si vous prétendez qu'il s'agit d'un comportement indéfini, j'aimerais voir une preuve (citation de la norme).
hmuelner
8
@hmuelner: Malheureusement, Armen et Jalf ont raison sur celui-ci. De [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>
Ben Voigt
1
Ce qui est drôle, car je pensais en fait que le comportement dépendait de la présence d'un destructeur non trivial (en particulier, que les classes POD pourraient être détruites via un pointeur vers la base).
Ben Voigt
3

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 deleteun 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):

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, 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'un share_ptrou unique_ptr. Bref, ne pas deletetout ce que vous n'avez pas directement à partir new.

user877329
la source
2

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.

jcoder
la source
2

J'ai également hérité de std::vectorré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!

Joel Andersson
la source
10
D'après mon expérience, les pires dommages à long terme sont souvent causés par des personnes qui essaient quelque chose de mal avisé, et " jusqu'à présent, n'ont pas eu (lu remarqué ) de problèmes avec cela".
Désillusionné le
0

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:

  1. Permettez-moi d'ajouter une autre façon d'encapsuler std::vectorsans écrire beaucoup d'encapsuleurs 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::vectoret éviter le problème dtor.
JiaHao Xu
la source
0

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

  std::vector<T> names; names.reserve(1000);

On peut dire

  template<typename C> 
  struct reserve_in : C { 
    reserve_in(std::size_t n) { this->reserve(n); }
  };

et puis, même en tant que membres de la classe,

  . . .
  reserve_in<std::vector<T>> taken_names{1000};  // 1
  std::vector<T> given_names{reserve_in<std::vector<T>>{1000}}; // 2
  . . .

(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_inest mieux servi par un modèle de fonction gratuit:

  template<typename C> 
  auto reserve_in(std::size_t n) { C c; c.reserve(n); return c; }

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:

  auto given_names{reserve_in<std::vector<T>>(1000)}; // 2
Nathan Myers
la source