Designfrage - Menge als Rückgabewert
-
@pumuckl Die Funktion hat nur eine Aufgabe, und die Klasse drum herum ist auch nur dazu da, diese Aufgabe zu supporten(am Ende speichert die Klasse im ctor nur ein paar Parameter um den Algorithmus zu konfigurieren).
Das Problem ist, dass das Ergebnis des Algorithmus fast eine beliebig lange Liste sein kann(zwischen 1000 und ~10000 Vektoren). Es muss also schon ein dynamischer Container sein. Auch ist die Datenmenge groß genug, dass ich es mir mehrmals überlegen würde, sie zu kopieren, also fällt ein Container als Rückgabewert raus. Hinzu kommt, dass wenn ein anderer container eingesetzt wird, auch anderer Code direkt davon betroffen ist. Also nicht wirklich die beste Wahl. Alles andere wirkt auf mich aber auch ziemlich unschön.
-
otze schrieb:
Zusätzliche Anforderung ist, dass jedes Modul problemlos ausgetauscht werden kann, ohne dass es sich auf andere Module auswirkt.
D.h., auch ohne daß andere Module neukompiliert werden müßten?
Die Lösung würde ich davon abhängig machen, wie du dir die Besitzverhältnisse vorstellst. Wenn es sinnvoll ist, nur eine Range auf einen intern verwalteten Container beliebigen Typs zurückzugeben, würde ich mir einen Iterator-Wrapper schreiben, der beliebige Iteratortypen durch ein gemeinsames binäres Interface nutzbar macht, z.B. im Enumerator-Stil:
template <typename T> class IEnumerator { public: virtual void reset (void) = 0; virtual bool moveNext (void) = 0; virtual T& get (void) = 0; }; typedef std::tr1::scoped_ptr <IEnumerator> IEnumeratorPtr; template <typename T, typename IterT = T*> class IteratorEnumerator : public IEnumerator { ... public: IteratorEnumerator (IterT first, IterT last) { ... } ... }; class Foo { public: IEnumeratorPtr bar (Image image); };Wenn aber das Besitztum des Containers auf den Aufrufer übergehen soll, wäre so etwas vielleicht besser:
template <typename T, typename ContT> class ContainerEnumerator : public IEnumerator { private: std::tr1::scoped_ptr <ContT> cont; ... public: IteratorEnumerator (std::tr1::scoped_ptr <ContT> _cont) : cont (_cont) { ... } ... };
-
otze schrieb:
Auch ist die Datenmenge groß genug, dass ich es mir mehrmals überlegen würde, sie zu kopieren, also fällt ein Container als Rückgabewert raus.
Dafür gibt's die "Return Value Optimization". Der MSVC kann's, der g++ laut kurzer Google-Nachfrage seit Version 3.1. Zum Lesen:
http://msdn.microsoft.com/en-us/library/ms364057%28VS.80%29.aspx
-
@Badestrand: dein Link beschreibt die NRVO. RVO != NRVO. Und RVO würde in diesem Fall auch vermutlich garnix bringen.
@otze: bist du sicher dass das Kopieren der Liste, im Vergleich mit dem Analysieren des Bildes, überhaupt lange dauert? Wenn das bloss ein paar Prozent sind, dann ... -> wurscht
-
hustbaer schrieb:
@Badestrand: dein Link beschreibt die NRVO. RVO != NRVO. Und RVO würde in diesem Fall auch vermutlich garnix bringen.
Upsi, da hab ich was durcheinandergeworfen. Aber wie wär's mit 'nem aktuellen Compiler, greift bei Objekt-Rückgabe nicht die Move-Semantik?
-
@ audacia:
scoped_ptrist nicht kopierbar (und zudem nicht in TR1).Was du wahrscheinlich meinst, ist
std::auto_ptr.
-
Nexus schrieb:
@ audacia:
scoped_ptrist nicht kopierbar (und zudem nicht in TR1).Tatsächlich - das war ein Flüchtigkeitsfehler; ich meinte natürlich
shared_ptr<>. Danke für den Hinweis.
-
Badestrand schrieb:
hustbaer schrieb:
@Badestrand: dein Link beschreibt die NRVO. RVO != NRVO. Und RVO würde in diesem Fall auch vermutlich garnix bringen.
Upsi, da hab ich was durcheinandergeworfen. Aber wie wär's mit 'nem aktuellen Compiler, greift bei Objekt-Rückgabe nicht die Move-Semantik?
Nur wenn die Klasse die als Return-Typ verwendet wird move-semantics unterstützt. Was die std:: Container tun sollten (und in der STD-Lib vom 2010 Beta auch implementiert ist -- GCC weiss ich nicht, vermutlich auch).
Ich denke das wäre eine gute Möglichkeit: sich drauf verlassen dass es eh egal ist, und sobald man einen Compiler hat der r-value refs kann, fällt der (vermutlich minimale) Overhead dann auch noch weg.
Falls man wirklich draufkommt, dass es nicht egal ist, kann man sich immernoch über diverse Workarounds ein "move-by-swap" für die Standard-Container basteln. Siehe boost::move_t<>.
-
Andere Möglichkeit wäre, den Container auf dem Heap zu erzeugen und einen Proxy zu schreiben, der die ganzen Implementierungsdetails (was für ein Container es wirklich ist usw.) kapselt. Schreib ein Interface, das den Ansprüchen der Clients genügt und implementiere es so, dass es den Performanceansprüchen genügt (move-Semantik usw.)
-
BTW:
Wenn der Container nur als Ausgabe verwendet würde, d.h. die Funktion da nur reinschreiben muss, dann wäre die Standard-Lösung natürlich nen Output-Iterator zu verwenden.
Wenn die Funktion wirklich den Container braucht, z.B. um nachsehen zu können ob es schon einen Eintrag gibt, dann würde man mit der Output-Iterator Variante natürlich wieder sinnlos Daten kopieren. Ist aber IMO wirklich die Frage ob das nicht egal ist. Vor allem da es dann eine sehr saubere Trennung gäbe.
----
Oder ... ganz doofer Vorschlag:
Du könntest die Funktion als Input-Iterator implementieren (mit lazy evaluation, oder auch ohne)
-
Vielleicht wäre auch
std::auto_ptreine Möglichkeit, Move-Semantik zu erzeugen. Eventuell reicht das ja.
-
Erstmal Danke für die Antworten. Ich habe leider immer noch keinen Favoriten rauspicken können. Der OutputIterator wäre sicherlich noch eine Idee die zumindest bei einigen Modulen für eine Verbesserung sorgen könnte. Andere wiederum speichern den Container auch intern um ihn zwischen den Aufrufen nur anzupassen, allerdings sollte in diesen Bereichen eigentlich die Datenmenge recht gering sein. Ich denke, ich werde das mal in der Gruppe ansprechen. Bislang sind wir mit dem Design noch nicht wirklich zufrieden
