Dilemma mit Rückgabewert
-
Abend,
ich habe eine Basisklasse A, mit folgender Methode:
virtual const std::vector<Foo>& getInnerFoos() const;
Das Problem ist: Ich will nicht, dass Unterklassen von A diese Methode überschreiben müssen. Sie kann also nicht pure virtual sein. Ich will also einen leeren Rumpf in A anbieten und Unterklassen können bei BEDARF getInnerFoos() überschreiben.
Das Problem ist aber die Referenz als Rückgabewert. Ich kann also nicht einfach einen leeren Rumpf in A anbieten. Muss ich jetzt extra in A eine dummy Variable (vector<Foo> mFoos;) anbieten, damit ich im Rumpf ein return mFoos machen kann?
-
Sinnvollerweise, ja. Allerdings könnte man das Design generell mal überdenken, wenn man die Funktion nur "optional" überschreiben kann, aber gar nicht garantiert ist, dass alle Klassen hier überhaupt etwas zum zurückgeben haben.
-
Wenn du eine Referenz auf eine Variable zurückgeben willst, muss diese Variable in der Klasse deklariert sein (also in A). Wenn du weder die Variable noch die Funktion brauchst, wieso deklarierst du sie nicht einfach in einer Kindklasse (so wie sie ist (die Funktion))?
-
Nun ja, es ist so: Manche Subklassen von A haben inner Foos, manche nicht. Die meisten haben es jedoch nicht.
Es gibt zusätzlich in A noch eine Check Methode: virtual bool hasInnerFoos();
Nur wenn die true liefert, wird getInnerFoos() aufgerufen.Da die meisten von A abgeleiteten Klassen keine inner Foos haben, würde ich gerne in A eine Defaultimplementierung anbieten:
virtual bool hasInnerFoos() { return false; } // das geht virtual const std::vector<Foo>& getInnerFoos() const { // ?? }getInnerFoos() soll in A gar nix machen. Es geht nur darum, dass es definiert ist. WENN jetzt eine Subklasse von A inner Foos hat, überschreibt sie beide Methoden (hasInnerFoos() gibt true zurück und getInnerFoos() dann einen vector).
Ich weiß nur nicht, wie ich eine leere Implementierung von A::getInnerFoos() anbieten soll.
-
Könntest Du nicht statt der Referenz auf die Foos einen Iterator liefern?
-
FooBarer schrieb:
Nun ja, es ist so: Manche Subklassen von A haben inner Foos, manche nicht.
Sag ich ja, das Design ist so schon merkwürdig. Üblicherweise sollten alle erbenden Klassen etwas sinnvolles mit dieser Methode machen können, das ist ja der Sinn von Vererbung.
Aber sei es: Du könntest auch einfach einen Pointer auf einen std::vector zurückgeben, der dann halt 0 sein kann.
-
Vielleicht einen anderen Rückgabetypen nehmen. Beispielsweise
std::vector<Foo>- Paar von Iteratoren (evtl. auch separate begin/end-Funktionen)
- Ranges
- Wrapper um Container von InnerFoos
-
Nexus schrieb:
Vielleicht einen anderen Rückgabetypen nehmen. Beispielsweise
std::vector<Foo>- Paar von Iteratoren (evtl. auch separate begin/end-Funktionen)
- Ranges
- Wrapper um Container von InnerFoos
Ist ein vector<Foo> getInnerFoos() { return mFoos; } nicht langsamer als ein
const vector<Foo>& getInnerFoos() { return mFoos; } ?
-
Wäre der Geschwindigkeitsunterschied denn so relevant, dass es sich dafür lohnen würde, einen hässlichen Workaround mit Dummy-Objekten zu implementieren?
Ansonsten fände ich Ranges oder einen range-ähnlichen Wrapper recht elegant, aber möglicherweise ist das zu fortgeschritten.
-
FooBarer schrieb:
getInnerFoos() soll in A gar nix machen. Es geht nur darum, dass es definiert ist.
Ja wie ist diese Schnittstelle denn definiert? Wenn Du diese Frage beantworten kannst, dann kennst Du auch Rückgabetyp und Implementierung. Was soll getInnerFoos() zurückgeben, wenn
Subklasse::hasInnerFoos() == false? Ist das ein ungültiger Aufruf? Dann würde ich mal das Design überdenken. Zum Beispiel getInnerFoos() in eine separate abstrakte Klasse auslagern, deren Interface dann von Subklassen, die innerFoos haben implementiert wird.class Base { // Foos ?? geht mich nix an }; struct HatFooInterface { virual FooContainer& getFoos() const = 0; }; class SubKlasseMitFoos : public Base, public HatFooInterface { // ... }; class SubKlasseOhneFoos : public Base { // ... }; void MachWasMitDenFoos(const HatFooInterface& objectDasAufJedenFallFoosHat) { objectDasAufJedenFallFoosHat.getFoos() // das geht immer! }Wenn die Schnittstelle
getInnerFoos()jedoch klar definiert ist, dass sie einen Container mit Foos zurückgibt, der i.A. leer ist, was spricht dann gegen etwas wieclass Base { public: const FooContainer& getFoos() { return foos_; } protected: FooContainer foos_; // kein dummy sondern DER Foo Container. };Alternativ kannst du definieren dass getFoos() i.A. einen nulltpr zurückgibt.
class Base { public: const FooContainer* getFoos() { return doGetFoos(); } private: virtual FooContainer* doGetFoos() { return nullptr; } };Davon würde ich aber abraten.
Abgesehen von dieser Frage (das ändert nichts daran, dass es im Grunde darum geht wie die Schnittstelle definiert wird) würde ich bei Rückgabetypen in Schnittstellen zu den von Nexus genannten raten; besonders Iteratoren bzw. Ranges. Ansonsten machst Du die ganze Welt abhängig von Deiner gewählten Container Schnittstelle.
-
FooBarer schrieb:
virtual bool hasInnerFoos() { return false; } // das geht virtual const std::vector<Foo>& getInnerFoos() const { // ?? }virtual bool hasInnerFoos() { return false; } // das geht virtual const std::vector<Foo>& getInnerFoos() const { throw ObjectDoesNotHaveInnerFoos("really! please call hasInnerFoos first next time"); return std::vector<Foo>();//get rid of compiler warnings }