J'ai une classe qui adapte std::vector pour modéliser un conteneur d'objets spécifiques au domaine. Je veux exposer la plupart de l'API std::vector à l'utilisateur, afin qu'il puisse utiliser des méthodes familières (size, clear, at, etc...) et des algorithmes standards sur le conteneur. Cela semble être un modèle récurrent pour moi dans mes conceptions :
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 conscient de la pratique consistant à préférer la composition à l'héritage lors de la réutilisation d'une classe pour l'implémentation - mais il doit y avoir une limite ! Si je devais tout déléguer à std::vector, il y aurait (d'après mes calculs) 32 fonctions de transfert !
Donc mes questions sont... Est-il vraiment si mauvais d'hériter de l'implémentation dans de tels cas ? Quels sont les risques ? Existe-t-il un moyen plus sûr d'implémenter ceci sans avoir à taper autant ? Suis-je un hérétique pour avoir utilisé l'héritage de l'implémentation :)
Editar:
Pourquoi ne pas préciser que l'utilisateur ne doit pas utiliser MyContainer via un pointeur std::vector<> :
// non_api_header_file.h
namespace detail
{
typedef std::vector<MyObject> MyObjectBase;
}
// api_header_file.h
class MyContainer : public detail::MyObjectBase
{
// ...
};
Les bibliothèques de boost semblent faire ce genre de choses tout le temps.
Edit 2 :
L'une des suggestions était d'utiliser des fonctions libres. Je vais le montrer ici sous forme de pseudo-code :
typedef std::vector<MyObject> MyCollection;
void specialCollectionInitializer(MyCollection& c, arguments...);
result specialCollectionFunction(const MyCollection& c);
etc...
Une façon plus OO 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.
}
6 votes
Oh goody ! Une autre chance de pousser mon blog à punchlet.wordpress.com - En gros, écrivez des fonctions libres et oubliez l'approche de l'enveloppe "plus OO". Ce n'est pas plus OO - si c'était le cas, il faudrait utiliser l'héritage, ce que vous ne devriez probablement pas faire dans ce cas. Rappelez-vous OO != classe.
2 votes
@Neil : Mais, mais les fonctions globales sont diaboliques !!! Tout est un objet ! ;)
4 votes
Ils ne seront pas globaux si vous les placez dans un espace de nom.
0 votes
@Neil : Je sais, je sais. Je faisais juste l'idiot. Un blog intéressant. Les "fonctions spéciales" dont je parle ici ne sont pas vraiment d'usage général et n'ont de sens que dans mon application particulière. C'est pourquoi j'hésite à en faire des fonctions libres et j'aimerais les regrouper dans une classe. J'ai révisé mon dernier exemple de code.
1 votes
Si vous voulez vraiment exposer toute l'interface du vecteur, il est probablement préférable en C++ d'utiliser la composition et d'exposer une référence au vecteur via un getter (avec des versions const et non const). En Java, il suffirait d'hériter, mais il n'est pas impossible qu'un crétin vienne ignorer votre documentation, supprime votre objet par le biais d'un mauvais pointeur (ou hérite à nouveau et sème la pagaille), et se plaigne ensuite. Pour un public limité, peut-être, mais si les utilisateurs sont des fous du polymorphisme dynamique, ou des programmeurs récemment ex-Java, vous concevez une interface dont vous pouvez être sûr qu'elle sera mal comprise.
1 votes
Vous ne pouvez pas vous protéger contre les personnes qui ignorent complètement la documentation. Je ne serais pas surpris de découvrir qu'une telle utilisation abusive cause autant de problèmes en Java qu'en C++.
0 votes
IMO C'est très bien. Personne ne devrait faire des pointeurs sur des vecteurs parce que les vecteurs stockent déjà leurs données de manière dynamique et qu'il n'y a pas de polymorphisme possible. La seule vraie raison d'utiliser un pointeur est
std::shared_ptr
et qui est conçu pour détruire correctement les types dérivés qui n'ont pas de destructeurs virtuels.