C ++ Problem mit Polymorphismus und Zeigervektoren
-
20-09-2019 - |
Frage
Betrachten Sie den folgenden Beispielcode:
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...
}
};
Ich habe ein solches Problem in meinem aktuellen Projekt. Der Client -Code verwendet BarCollection
, was Zeiger auf speichert Bars
in d_foos
das wird in deklariert FooCollection
. Ich möchte jetzt die Sammlung von Zeigern dem Client -Code an Bars aussetzen. Ich könnte dem Client -Code nur Zugriff auf den Vektor der Zeiger geben Foo
s und wirken Sie diese an Zeiger auf Bar
s im Client -Code, aber das fühlt sich falsch an, da der Kunde nichts wissen muss Foo
Existenz.
Ich könnte auch a definieren get()
Mitglied, das Objekte abruft d_foos
Und wirkt sie, aber das fühlt sich ziemlich ungeschickt an. Vorzugsweise möchte ich nur d_foos als zurückgeben vector<shared_ptr<Bar> > &
, aber ich kann das nicht tun.
Es könnte auch sein, dass mein Design einfach falsch ist. Es schien jedoch für die natürliche Lösung zu sein, als wie Bar
ist eine Spezialisierung von Foo
und BarCollection
ist eine Spezialisierung von FooCollection
Und sie teilen Funktionen.
Könnten Sie nette Lösungen für die Implementierung vorschlagen? getBars
in BarCollection
Oder bessere Designalternativen?
Bearbeiten:
Es stellte sich heraus, dass mein Design in der Tat schlecht war. Barcollection ist keine Foocollection, obwohl die gesamte Funktionalität von Foocollection erforderlich ist. Meine aktuelle Lösung basiert auf den folgenden Antworten - was viel sauberer ist - jetzt:
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.
};
Vielen Dank für all die hervorragenden Vorschläge und Beispiele!
Lösung
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> {
};
Andere Tipps
Ich würde empfehlen, Iteratoren aus Ihren Containerklassen anstelle des Mitgliedsbehälters auszusetzen. Auf diese Weise spielt es keine Rolle, was der Containertyp ist.
Das Problem ist, dass Sie versuchen, zwei verschiedene, ziemlich unabhängige Arten von Polymorphismus auf eine Weise zu mischen, die nicht funktioniert. Mit dem Typ-Safe-Polymorphismus von Templatten können Sie nicht einen Basistyp für einen abgeleiteten Typ ersetzen. Das Vorlagensystem von C ++ macht keinen Zusammenhang zwischen
class<Foo>
und
class<Bar>
Ein Vorschlag könnte darin bestehen, einen von Foo abgeleiteten Adapter zu erstellen, der auf die richtige Klasse abweist:
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);
}
};
Beachten Sie, dass diese Klasse das gleiche Problem wie ein Iterator hat. Der Betrieb im Vektor kann diese Klasse ungültig machen.
Ein weiterer Gedanke
Sie werden es vielleicht nicht merken, aber es ist vielleicht vollkommen in Ordnung, nur einen Vektor von Foo zurückzugeben. Der Nutzer von Bar muss bereits über volle Kenntnisse in Foo verfügen, da sie durch Bar.H Foo.h durch Bar.H. Der Grund dafür ist, dass die Bar, um von Foo von Foo zu erben, über Foo.h. Ich würde vorschlagen, anstatt die obige Lösung zu verwenden, wenn es möglich ist, Foo (oder eine Superklasse von Foo) zu einer Schnittstellenklasse zu machen und um Vektoren von Zeigern an diese Schnittstellenklasse zu gelangen. Dies ist ein ziemlich häufig gesehenes Muster und hebt nicht die Augenbrauen, die diese wackelige Lösung, die ich mir ausgedacht habe, vielleicht :). Andererseits haben Sie möglicherweise Ihre Gründe. Viel Glück oder so.
Die Frage ist, warum Sie das tun sollten? Wenn Sie dem Benutzer eine Sammlung von Zeigern in die Bar geben, gehen Sie davon aus, dass nur Bars darin enthalten sind, so dass das interne Speichern der Zeiger in einer Sammlung zu Foo keinen Sinn ergibt. Wenn Sie verschiedene Subtypen von Foo in Ihrer Zeigersammlung auf FOO speichern, können Sie es nicht als Sammlung von Zeigern in Bar zurückgeben, da nicht alle Objekte dort Bars sind. Im ersten Fall (Sie wissen, dass Sie nur Balken haben) sollten Sie wie oben vorgeschlagen einen Vorlagenansatz verwenden. Ansonsten musst du überdenken, was du wirklich willst.
Können Sie dies nicht durch eine Sammlung ersetzen, die in der beiden Foo / Bar templatisiert ist ?, Etwas wie diese
class Collection<T> {
protected:
vector<shared_ptr<T> > d_foos;
};
typedef Collection<Foo> FooCollection;
typedef Collection<Bar> BarCollection;
Haben Sie ein besonderes Bedürfnis zu haben? BarCollection
abgeleitet von FooCollection
? Weil im Allgemeinen a BarCollection
ist nicht a FooCollection
, normalerweise viele Dinge, die mit a gemacht werden können FooCollection
sollte nicht mit a gemacht werden BarCollection
. Zum Beispiel:
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
Jetzt haben wir a hinzugefügt Foo
Objekt gegen das, was ein sein soll BarCollection
. Wenn die BarCollection
versucht, auf dieses neu hinzugefügte Element zuzugreifen und erwartet, dass es a ist Bar
, Es werden alle möglichen hässlichen Dinge passieren.
Normalerweise möchten Sie dies vermeiden und haben Ihre Sammelklassen nicht voneinander abgeleitet. Siehe auch Fragen um Container gießen von abgeleiteten Typen für weitere Antworten zu diesem Thema ...
Lassen Sie uns zunächst darüber sprechen shared_ptr
. Weißt du Bescheid über: boost::detail::dynamic_cast_tag
?
shared_ptr<Foo> fooPtr(new Bar());
shared_ptr<Bar> barPtr(fooPtr, boost::detail::dynamic_cast_tag());
Dies ist ein sehr praktischer Weg. Unter dem Cover führt es nur eine durch dynamic_cast
, nichts Besonderes dort als eine einfachere Notation. Der Vertrag ist der gleiche wie der Klassiker: Wenn das Objekt darauf hingewiesen hat Bar
(oder abgeleitet davon), dann bekommen Sie einen Nullzeiger.
Zurück zu Ihrer Frage: schlechter Code.
BarCollection
ist kein FooCollection
, wie bereits erwähnt, dass Sie daher in Schwierigkeiten sind, weil Sie andere Elemente im Vektor von Zeigern einführen können, die Bar
Einsen.
Ich werde das jedoch nicht ausdehnen, denn dies geht über die Frage hinaus, und ich denke, dass wir (als diejenigen, die versuchen zu antworten) uns davon zurückhalten sollten.
Sie können keine Referenz übergeben, aber Sie können a passieren View
.
Grundsätzlich a View
ist ein neues Objekt, das als Proxy
zum alten. Es ist relativ einfach mit Boost.iteratoren Aus Beispiel.
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
Das eigentliche Problem hier ist natürlich das reference
bisschen. A NEW
shared_ptr
Objekt ist einfach und ermöglicht es, eine auszuführen dynamic_cast
nach Bedarf. A reference
zum ORIGINAL
shared_ptr
Aber als der erforderliche Typ interpretiert ... ist wirklich nicht das, was ich im Code sehe.
Notiz:
Es könnte eine Möglichkeit geben, mit Boost.fusion besser zu machen Transformation_View Klasse, aber ich konnte es nicht herausfinden.
Insbesondere verwenden transform_view
ich kann erhalten shared_ptr<Bar>
Aber ich kann keine bekommen shared_ptr<Bar>&
Wenn ich der Dereferenz meines Iterators, was ärgerlich ist, da die einzige Verwendung der Rückgabe eines Verweis vector
(und nicht a const_reference
) ist tatsächlich die Struktur der Struktur der vector
und die Objekte, die es enthält.
Anmerkung 2:
Bitte beachten Sie, dass Refactoring. Dort gab es hervorragende Vorschläge.