Est-il possible d'hériter de la mise en œuvre de conteneurs STL, plutôt que de déléguer?
J'ai une classe qui s'adapte std::vector pour modèle un conteneur de domaine d'objets spécifiques. Je veux exposer la plupart des std::vector API pour l'utilisateur, afin qu'il/elle peut utiliser les méthodes habituelles (taille, clair, à, etc...) et des algorithmes standard sur le conteneur. Cela semble être un motif récurrent pour moi dans mes dessins:
class MyContainer : public std::vector<MyObject>
{
public:
//Redeclare all container traits: value_type, iterator, etc...
//Domain-specific constructors
//(more useful to the user than std::vector ones...)
//Add a few domain-specific helper methods...
//Perhaps modify or hide a few methods (domain-related)
};
Je suis au courant de la pratique de préférant la composition à l'héritage lors de la réutilisation d'une classe pour la mise en œuvre-mais il doit bien y avoir une limite! Si je ont été de tout déléguer à std::vector, il y aurait (selon mes calculs) 32 fonctions de transfert!
Donc mes questions sont... Est-il vraiment si mauvais pour hériter de la mise en œuvre dans de tels cas? Quels sont les risques? Est-il plus sûr moyen pour que je puisse mettre en œuvre ce sans pour autant taper? Je suis un hérétique pour l'utilisation de la mise en œuvre de l'héritage? 🙂
Edit:
Qu'en indiquant clairement que l'utilisateur ne doit pas utiliser MyContainer par l'intermédiaire d'un std::vector<> pointeur:
//non_api_header_file.h
namespace detail
{
typedef std::vector<MyObject> MyObjectBase;
}
//api_header_file.h
class MyContainer : public detail::MyObjectBase
{
//...
};
Les bibliothèques boost semblent faire ce genre de choses tout le temps.
Edit 2:
L'une des suggestions était d'utiliser les fonctions libres. Je vais montrer ici, sous forme de pseudo-code:
typedef std::vector<MyObject> MyCollection;
void specialCollectionInitializer(MyCollection& c, arguments...);
result specialCollectionFunction(const MyCollection& c);
etc...
Un plus OO façon de le faire:
typedef std::vector<MyObject> MyCollection;
class MyCollectionWrapper
{
public:
//Constructor
MyCollectionWrapper(arguments...) {construct coll_}
//Access collection directly
MyCollection& collection() {return coll_;}
const MyCollection& collection() const {return coll_;}
//Special domain-related methods
result mySpecialMethod(arguments...);
private:
MyCollection coll_;
//Other domain-specific member variables used
//in conjunction with the collection.
}
- Oh génial! Une autre chance de pousser mon blog à punchlet.wordpress.com - fondamentalement, écrire des fonctions libres, et d'oublier les "plus OO" wrapper approche. Il n'est pas plus OO - si c'était elle serait d'utiliser l'héritage, qui vous ne devriez probablement pas dans ce cas. Rappelez-vous OO != classe.
- Mais, mais.. fonctions globales mal!!! Tout est un objet! 😉
- Ils ne seront pas global, si vous les mettez dans un espace de noms.
- Je sais, je sais. Était juste tourner autour. Blog intéressant. Les "fonctions spéciales" dont je parle ici, mais ne sont pas vraiment à usage général et n'ont de sens que dans mon application en particulier. C'est pourquoi j'hésite à les rendre libres les fonctions et souhaitez les regrouper dans une classe. J'ai révisé mon dernier exemple de code.
- Si vraiment vous ne souhaitez exposer l'ensemble de l'interface de vecteur, alors il est probablement mieux en C++ pour utiliser la composition, et d'exposer une référence pour le vecteur par un getter (avec const et non const versions). En Java tu venais d'hériter, mais alors en Java certains numpty de ne pas venir, ignorer votre documentation, de supprimer votre objet via le pointeur incorrect (ou d'en hériter à nouveau et mess it up), et ensuite se plaindre. Pour un public restreint, peut-être, mais si les utilisateurs pourraient être dynamique-polymorphisme des monstres, ou, récemment, l'ex-programmeurs Java, vous êtes en train de concevoir une interface que vous pouvez être assez sûr que ils ne pas comprendre.
- Vous ne pouvez pas les protéger contre les gens complètement ignorant de la documentation. Je ne vais pas être surpris de trouver une telle utilisation abusive provoque tant de problèmes en Java qu'en C++.
- IMO C'est très bien. Nul ne devrait être prise pointeurs de vecteurs en raison des vecteurs déjà stocker leurs données de manière dynamique et il n'y a pas de polymorphisme possible. La seule vraie raison de l'utilisation d'un pointeur est
std::shared_ptr
et qui est conçue pour correctement détruire les types dérivés qui n'ont pas les destructeurs virtuels.
Vous devez vous connecter pour publier un commentaire.
Le risque est en libérant de l'aide d'un pointeur vers la classe de base (supprimer, delete[], et potentiellement d'autres méthodes de libération de la mémoire). Depuis ces classes (deque, carte, chaîne, etc.) n'ont pas de virtuel dtors, il est impossible de les nettoyer correctement avec seulement un pointeur vers ces classes:
Cela dit, si vous êtes prêt à vous assurer de ne jamais accidentellement ce faire, il y a peu d'inconvénient majeur, à l'héritage, mais dans certains cas c'est un grand si. D'autres inconvénients comprennent des affrontements avec la mise en œuvre des détails et des extensions (certains peuvent ne pas utiliser les identificateurs réservés) et de traiter avec ballonnement interfaces (chaîne en particulier). Toutefois, l'héritage est prévu dans certains cas, en tant que contenant des adaptateurs comme pile ont un membre protégé c (le conteneur sous-jacent-ils s'adapter), et c'est presque uniquement accessible à partir d'une instance de la classe dérivée.
Au lieu de l'héritage ou de la composition, envisager d'écrire des fonctions libres qui peut prendre soit un itérateur paire ou un conteneur de référence, et sur cette. Pratiquement tous les <algorithm> est un exemple de cela, et make_heap, pop_heap, et push_heap, en particulier, sont un exemple d'utilisation de fonctions libres au lieu d'un domaine d'un conteneur spécifique.
Utilisez le conteneur des classes pour vos types de données, et encore appeler des fonctions gratuites pour votre domaine spécifique de la logique. Mais vous pouvez encore atteindre une modularité à l'aide d'une définition de type, ce qui vous permet à la fois de simplifier les déclarant et fournit un point unique si une partie d'entre eux a besoin de changer:
Avis de la value_type et l'allocateur peut changer sans affecter plus tard, le code à l'aide de la définition de type, et même le conteneur peut changer d'un deque à un vecteur.
Vous pouvez combiner privé de l'héritage et de la 'l'aide de mot-clé pour contourner la plupart des problèmes mentionnés ci-dessus: Privé de l'héritage est" est mis en œuvre en termes de " et que c'est privé, vous ne pouvez pas tenir un pointeur vers la classe de base
private
l'héritage est encore l'héritage et donc une plus forte relation de composition. Notamment, cela signifie que la modification de la mise en œuvre de votre classe est nécessairement d'aller casser la compatibilité binaire.Comme tout le monde l'a déjà dit, des conteneurs STL n'ont pas les destructeurs virtuels afin d'hériter d'eux est dangereux à mieux. J'ai toujours considéré que la programmation générique avec des modèles comme un style différent de OO - l'un sans l'héritage. Les algorithmes de définir l'interface dont ils ont besoin. Il est aussi proche de Duck-Typing que vous pouvez obtenir dans un langage statique.
De toute façon, j'ai quelque chose à ajouter à la discussion. La manière que j'ai créé mon propre template de spécialisations précédemment est de définir des classes comme suit pour l'utiliser comme base de classes.
Ces classes exposent la même interface que un conteneur STL. Je n'ai comme l'effet de la séparation de la modification et de la non-modification des opérations en différentes classes de base. Cela a un très bon effet sur la const-correctness. Le seul inconvénient est que vous devez étendre l'interface si vous souhaitez utiliser ces avec les conteneurs associatifs. Je n'ai pas couru dans la nécessité de bien.
Dans ce cas, l'héritage est une mauvaise idée: les conteneurs STL n'ont pas de virtuel destructeurs de sorte que vous pourriez avoir des fuites de mémoire (et en plus, c'est une indication que les conteneurs STL ne sont pas destinés à être héréditaire, en premier lieu).
Si vous avez juste besoin d'ajouter certaines fonctionnalités, vous pouvez le déclarer dans les méthodes globales, ou une classe légère avec un conteneur membre pointeur/référence. Ce cours ne vous permet pas de vous cacher méthodes: si c'est vraiment ce que vous êtes après, alors il n'y a aucune autre option alors redeclaring la mise en œuvre complète.
-Dprivate=public
sur le compilateur de ligne de commande. spécificateurs d'Accès comme privés, sont pour la plupart de la documentation, qui arrive juste à être exécutée.Virtuel dtors côté, la décision d'hériter de rapport contiennent une décision de conception en fonction de la classe que vous créez. Vous ne devriez jamais hériter conteneur fonctionnalité juste parce qu'elle est plus facile que contenant un récipient et ajouter un peu d'ajouter et de supprimer des fonctions qui semblent comme simpliste wrappers sauf vous pouvez définitivement dire que la classe que vous créez est une sorte de conteneur. Par exemple, une classe de classe contiennent souvent des étudiants des objets, mais une salle de classe n'est pas une sorte de liste d'élèves pour la plupart des fins, de sorte que vous ne devriez pas être hérite de la liste.
Il est plus facile de le faire:
Le transfert des méthodes inline loin, de toute façon. Vous ne serez pas obtenir de meilleures performances de cette façon. En fait, vous obtiendrez probablement une dégradation des performances.