Parameterübergabe mit Besitzübergabe
-
Hallo zusammen,
welche Art der Übergabe wäre hier sinnvoller, wenn der Aufrufer den Besitz am Datenobjekt nicht benötigt?
#include "DataFactory.h" #include <map> #include <memory> class Data; // Datengröße: 5-500 Bytes class DataProcessor { //... public: // Daten hinzufügen und bearbeiten (Aufruf 1-100x/Sekunde) void AddData(Data& data); // mach Dir eine Kopie void AddData(Data* data); // Du übernimmst das löschen! void AddData(const std::tr1::shared_ptr<Data>& data); void AddData(const DataFactory& dataFactory, ... /*Paramater*/); // mach Dir das Datenobjekt selber! //... private: std::map<int, Data> DataMap; std::map<int, Data*> DataPtrMap; std::map<int, std::tr1::shared_ptr<Data>> SharedDataPtrMap; };
-
std::map<int, Data> DataMap; // Data is billig zu kopieren und ist ein Value Object, d.h. Identität spielt keine Rolle std::map<int, Data*> DataPtrMap; // Data hat Identität oder ist teuer zu kopieren. // Die Lebensdauer wird von einer Drittklasse kontrolliert und dauert länger an als die DataProcessor Instanz. std::map<int, std::tr1::shared_ptr<Data>> SharedDataPtrMap; // Die Frage nach dem Besitz kann nicht eindeutig geklärt werden. // Die Instanz kann von "mir" gelöscht werden oder auch woanders.
Es klingt ein wenig so, als würde bei dir eine Fabrik die Daten Instanz erzeugen und dem DataProcessor zum speichern übergeben. In dem Fall klingt für mich die erste Variante am sinnvollsten, vorausgesetzt du benötigst keine Polymorphie. Statt
std::map<int, Data*>
kannst da ansonsten auch nach den boost ptr containern gucken, falls einshared_ptr
tatsächlich zu teuer sein sollte (was ich hier eigentlich eher ausschließe. 1-100 Aufrufe pro Sekunde is quasi goar nix) oder falls es dein Compiler kann einunique_ptr
.Die Variante
AddData(const DataFactory& dataFactory, ...);
macht nur Sinn, wenn DataFactory eine abstrakte Fabrik ist, sie also durch andere Fabriken austauschbar ist. Ansonsten ist die Abhängigkeit zur Fabrik völlig unnötig.
-
Hallo brotbernd,
Data
ist eine abstrakte Basisklasse. Die eigentlichen Datenobjekte werden vom Aufrufer direkt erzeugt. Eine Fabrik wäre aber auch möglich. Nachdem die Datenobjekte an denDataProcessor
übergeben wurden, benötigt der Aufrufer diese nicht mehr.Über eine Callback-Funktion wird der Aufrufer über Änderungen an
Data
informiert. Dabei weiß ich auch noch nicht, ob diese Callback-Funktion imData
-Objekt oder imDataProcessor
gespeichert werden soll.Bei einer voraussichtlichen Objektgröße von bis zu 500 Bytes und bei 100
AddData()
-Aufrufen/Sekunde, würdest Du die Übergabe als Referenz wählen und die Kopien in Kauf nehmen? Habe ich das richtig verstanden?
-
Unterstüzt dein Compiler schon Move-Semantik? Dann kannst du die Objekte schön erst in den DataProcessor und dann in die Map moven.
-
Hallo ipsec,
jetzt muss ich zugeben, dass mir "Move-Semantik" nichts sagt...
--> *google*
IDE/Compiler: Visual Studio 2008 Pro
-
Move-Semantik kommt erst mit C++0x und RValue-Referenzen. VS unterstützt sie leider erst ab 2010.
-
Wenn Data eine abstrakte Basisklasse ist, kannst du kein
std::map<int, Data> DataMap;
machen, da es von Data keine Instanzen geben kann. Also entwederstd::map<int, shared_ptr<Data>> DataMap;
eine boost::ptr_map oder eine map mit unique_ptr.
Ich würde dir raten erstmal einenshared_ptr<Data>
alsDataPtr
zu definieren und einestd::map<int, DataPtr>
zu benutzen. Deine Anwendung klingt nicht so, als sei ein Referenzzähler ein Flaschenhals.
-
Ich danke Euch!
Da ich
boost
hier nicht so leicht durchsetzen kann, werde ich es mit demshared_ptr
probieren.Dass ein STL-Container keine abstrakte Basisklasse aufnehmen kann, hatte ich gar nicht auf dem Schirm. *ups*
Der
shared_ptr
hat den Vorteil, dass derDataProcessor
nach der Bearbeitung den Aufrufer über die Bearbeitung informieren kann (via Callback-Funktion) und dabei das Datenobjekt mit an den Aufrufer übergeben kann. DerDataProcessor
kann das Datenobjekt dann aus seiner Map löschen, ohne darauf achten zu müssen, ob der Aufrufer noch auf das Datenobjekt zugreift.
-
void AddData(Data& data); // mach Dir eine Kopie
Dann würde ich aber Referenzen auf
const
nehmen.Was ist mit
std::auto_ptr
? Der ist genau dafür da, Besitz zu transferieren. In C++0x wird er durchstd::unique_ptr
abgelöst. Ausserdem kannst dustd::tr1::shared_ptr
von ihm konstruieren.void AddData(std::auto_ptr<Data> data);
-
Nexus schrieb:
void AddData(Data& data); // mach Dir eine Kopie
Dann würde ich aber Referenzen auf
const
nehmen.Stimmt, Du hast vollkommen recht.
Nexus schrieb:
Was ist mit
std::auto_ptr
? Der ist genau dafür da, Besitz zu transferieren. In C++0x wird er durchstd::unique_ptr
abgelöst. Ausserdem kannst dustd::tr1::shared_ptr
von ihm konstruieren.void AddData(std::auto_ptr<Data> data);
An den
auto_ptr
habe ich eben auf der Heimfahrt auch gedacht. Aber nachdem ich nochmal bei Effektiv C++ nachgeschlagen habe und S.Meyers wegen dem Kopierverhalten und der STL-Container-Tauglichkeit lieber einshared_ptr
verwendet, habe ich den Gedanken wieder verworfen.Aber Du hast Recht: Man könnte aus dem
auto_ptr<Data>
inAddData
ein shared_ptr<Data> machen und dann in die std::map tun. Dann sieht die Schnittstelle besser aus (Besitzübergabe, statt geteilter Besitz).
-
Roger Wilco schrieb:
Aber nachdem ich nochmal bei Effektiv C++ nachgeschlagen habe und S.Meyers wegen dem Kopierverhalten und der STL-Container-Tauglichkeit lieber ein
shared_ptr
verwendet, habe ich den Gedanken wieder verworfen.Ja, in einem STL-Container taugt
std::auto_ptr
nichts. Sonst ist er manchmal praktisch. Wobei ich in meinem C++98-Code einen etwas anderen Smart-Pointer mit verschobenem Besitz gebaut habe, der ein paar Probleme vonstd::auto_ptr
behebt.Roger Wilco schrieb:
Dann sieht die Schnittstelle besser aus (Besitzübergabe, statt geteilter Besitz).
Vor allem wird auch dem Aufrufer klargemacht, dass er den Besitz abgibt (wegen "wenn der Aufrufer den Besitz am Datenobjekt nicht benötigt" hielt ich das für wichtig).