c ++ problème avec polymorphisme et vecteurs de pointeurs
-
20-09-2019 - |
Question
Prenons l'exemple de code suivant:
class Foo
{
};
class Bar : public Foo
{
};
class FooCollection
{
protected:
vector<shared_ptr<Foo> > d_foos;
};
class BarCollection : public FooCollection
{
public:
vector<shared_ptr<Bar> > &getBars()
{
// return d_foos won't do here...
}
};
J'ai un problème comme celui-ci dans mon projet en cours. Le code client utilise BarCollection
, qui stocke des pointeurs vers Bars
dans d_foos
qui est déclaré dans FooCollection
. Je voudrais maintenant exposer la collection de pointeurs vers des barres au code client. Je pouvais donner le code client l'accès au vecteur de pointeurs vers Foo
s et jeter ceux-ci à des pointeurs vers Bar
s dans le code client, mais cela ne semble pas correct puisque le client n'a pas à connaître l'existence de Foo
.
Je pourrais aussi définir un élément de get()
qui récupère les objets de d_foos
et les jette, mais cela se sent tout à fait maladroite. De préférence, je voudrais revenir juste d_foos comme vector<shared_ptr<Bar> > &
, mais je ne peux pas sembler le faire.
Il se pourrait aussi que ma conception est tout simplement faux. Il semblait la solution la plus naturelle que, comme Bar
est une spécialisation de Foo
et BarCollection
est une spécialisation de FooCollection
et ils partagent la fonctionnalité.
Pouvez-vous suggérer des solutions agréables pour mettre en œuvre getBars
dans BarCollection
ou de meilleures alternatives de conception?
Modifier
Il s'avère que ma conception était mauvais effet. BarCollection n'est pas un FooCollection, en dépit d'exiger toutes les fonctionnalités de FooCollection. Ma solution actuelle basée sur les réponses ci-dessous - ce qui est beaucoup plus propre - est maintenant:
class Foo
{
};
class Bar : public Foo
{
};
template<class T>
class Collection
{
vector<shared_ptr<T> > d_items;
};
typedef Collection<Foo> FooCollection;
class BarCollection : public Collection<Bar>
{
// Additional stuff here.
};
Merci pour toutes les excellentes suggestions et des exemples!
La solution
template<class T>
class MyContainer {
vector<shared_ptr<T> > d_foos;
public:
vector<shared_ptr<T> > & getVector();
};
class FooCollection : public MyContainer<Foo> {
};
class BarCollection : public MyContainer<Bar> {
};
Autres conseils
Je suggère d'exposer itérateurs de vos classes de conteneurs, au lieu du conteneur membre. De cette façon, il ne sera pas question ce que le type de conteneur est.
Le problème est que vous essayez de mélanger et match deux différents types joli beaucoup indépendants de polymorphisme d'une manière qui ne fonctionnera pas. La compilation, le polymorphisme de type sécurisé de modèles ne vous permettra pas de substituer un type de base pour un type dérivé. système de modèle de C ++ ne fait aucune association entre
class<Foo>
et
class<Bar>
Une suggestion pourrait être de créer un adaptateur dérivé de Foo qui renversera la classe correcte:
template <class derived, class base>
class DowncastContainerAdapter
{
private:
std::vector< boost::shared_ptr<base> >::iterator curr;
std::vector< boost::shared_ptr<base> >::const_iterator end;
public:
DowncastContainerAdapter(/*setup curr & end iterators*/)
{
// assert derived actually is derived from base
}
boost::shared_ptr<derived> GetNext()
{
// increment iterator
++curr;
return dynamic_cast<base>(*curr);
}
bool IsEnd()
{
return (curr == end);
}
};
Notez cette classe aura le même problème que itérateur, opération sur le vecteur peut invalider cette classe.
Une autre pensée
Vous ne pouvez pas le réaliser, mais il peut être parfaitement bien de revenir juste un vecteur de Foo. L'utilisateur du bar doit déjà avoir une connaissance complète de Foo, car en incluant Bar.h ils doivent obtenir .h par Bar.h. La raison étant que pour Bar à hériter de Foo, il doit avoir une connaissance complète de la classe via Foo.h. Je suggère plutôt que d'utiliser la solution ci-dessus, s'il est possible faire Foo (ou un super classe de Foo) une classe d'interface et passer autour des vecteurs de pointeurs vers cette classe d'interface. Ceci est un modèle vu assez souvent et ne soulèvera pas les sourcils que cette solution bancale je suis venu avec peut :). Là encore, vous pouvez avoir vos raisons. Bonne chance dans les deux cas.
La question est, pourquoi voudriez-vous faire? Si vous donnez à l'utilisateur une collection de pointeurs à la barre, vous assumez, qu'il n'y a que Bars, donc le stockage interne des pointeurs dans une collection Foo n'a pas de sens. Si vous stockez différents sous-types de Foo dans votre collection de pointeur vers Foo, vous ne pouvez pas revenir comme une collection de pointeurs vers Bar, car tous les objets il y a des bars. Dans le premier cas, (vous savez que vous avez seulement des bars), vous devez utiliser une approche modélisée comme suggéré ci-dessus. Dans le cas contraire, il faut repenser, ce que vous voulez vraiment.
Pouvez-vous remplacer pas avec une collection de chaque Foo sans canevas / Bar ?, quelque chose comme ceci
class Collection<T> {
protected:
vector<shared_ptr<T> > d_foos;
};
typedef Collection<Foo> FooCollection;
typedef Collection<Bar> BarCollection;
Avez-vous un besoin particulier d'avoir BarCollection
dérivé de FooCollection
? Parce que généralement un BarCollection
est pas FooCollection
, généralement beaucoup de choses qui peut être fait avec un FooCollection
ne doit pas être fait avec un BarCollection
. Par exemple:
BarCollection *bc = new BarCollection();
FooCollection *fc = bc; // They are derived from each other to be able to do this
fc->addFoo(Foo()); // Of course we can add a Foo to a FooCollection
Maintenant, nous avons ajouté un objet Foo
à ce qui est censé être un BarCollection
. Si le BarCollection
tente d'accéder à cet élément nouvellement ajouté et attend que ce soit un Bar
, toutes sortes de vilaines choses vont se produire.
Donc, en général vous voulez éviter cela et ne pas vos classes de collection dérivées de l'autre. Voir aussi à propos de
Tout d'abord, parlons Ceci est une façon très pratique. Sous le couvercle, il effectue juste un Retour à votre question. CODE BAD Je ne prolongerons pas sur ce bien, parce que cela est au-delà de la question à portée de main, et je pense que nous (comme ceux qui tentent de répondre) nous devons retenir de cela. Vous ne pouvez pas passer une référence, mais vous pouvez passer un En fait, un Le vrai problème est ici bien sûr le bit Remarque : En particulier, en utilisant Note 2 : shared_ptr
. Connaissez-vous: boost::detail::dynamic_cast_tag
shared_ptr<Foo> fooPtr(new Bar());
shared_ptr<Bar> barPtr(fooPtr, boost::detail::dynamic_cast_tag());
dynamic_cast
, rien là de fantaisie, mais une notation plus facile. Le contrat est le même que le classique: si l'objet pointé n'est pas réellement un Bar
(ou dérivé de celui-ci), vous obtenez un pointeur NULL BarCollection
n'est pas un FooCollection
, comme mentionné, vous êtes donc en difficulté parce que vous pouvez introduire d'autres éléments dans le vecteur des pointeurs qui Bar
les. View
. View
est un nouvel objet qui agissent comme Proxy
à l'ancienne. Il est relativement facile en utilisant Boost.Iterators de par exemple. class VectorView
{
typedef std::vector< std::shared_ptr<Foo> > base_type;
public:
typedef Bar value_type;
// all the cluttering
class iterator: boost::iterator::iterator_adaptor<
iterator,
typename base_type::iterator,
std::shared_ptr<Bar>
>
{
typename iterator_adaptor::reference dereference() const
{
// If you have a heart weakness, you'd better stop here...
return reinterpret_cast< std::shared_ptr<Bar> >(this->base_reference());
}
};
// idem for const_iterator
// On to the method forwarding
iterator begin() { return iterator(m_reference.begin()); }
private:
base_type& m_reference;
}; // class VectorView
reference
. Obtenir un objet NEW
de shared_ptr
est facile et permet d'effectuer une dynamic_cast
au besoin. Obtenir un reference
au ORIGINAL
de shared_ptr
mais interprété comme le type requis ... est vraiment pas ce que je veux voir dans le code.
Il pourrait y avoir un moyen de faire mieux que cela en utilisant Boost.Fusion transform_view classe, mais je ne pouvais pas comprendre. transform_view
je peux obtenir shared_ptr<Bar>
mais je ne peux pas obtenir un shared_ptr<Bar>&
quand je déréférencer mon iterator, ce qui est gênant, étant donné que l'utilisation de retourner seulement une référence à la vector
sous-jacente (et non un const_reference
) est de modifier effectivement la structure du vector
et les objets qu'il contient.
S'il vous plaît envisager refactoring. Il y a eu d'excellentes suggestions là.