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 ein shared_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 ein unique_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 den DataProcessor ü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 im Data -Objekt oder im DataProcessor 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 entweder std::map<int, shared_ptr<Data>> DataMap; eine boost::ptr_map oder eine map mit unique_ptr.
    Ich würde dir raten erstmal einen shared_ptr<Data> als DataPtr zu definieren und eine std::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 dem shared_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 der DataProcessor nach der Bearbeitung den Aufrufer über die Bearbeitung informieren kann (via Callback-Funktion) und dabei das Datenobjekt mit an den Aufrufer übergeben kann. Der DataProcessor 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 durch std::unique_ptr abgelöst. Ausserdem kannst du std::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 durch std::unique_ptr abgelöst. Ausserdem kannst du std::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 ein shared_ptr verwendet, habe ich den Gedanken wieder verworfen.

    Aber Du hast Recht: Man könnte aus dem auto_ptr<Data> in AddData 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 von std::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).


Anmelden zum Antworten