Benutzen von Smart Pointer (std::shared_ptr)



  • Hallo zusammen,

    ich bin am neu Aufbauen unserer "InOut" und "Motion" Library. Diese Libraries haben jeweils eine externe Schnittstelle für die Applikationen und eine interne Schnittstelle für die Erweiterungskarten. Da die Pointer für beide Schnittstellen identisch sind, vermute ich, dass der shared_ptr hier die richtige Wahl ist.

    Beide Libraries werden durch eine XML Datei konfiguriert.

    Eine Funktion:

    VIOCreator* creator = Object<VIOCreator> (boardName);
    

    darf ich nicht ändern, da auch die alten Libraries diese Funktion benutzen.

    class VIOCreator;
    class VIOBoard;
    class VIOPort;
    class VIOChannel;
    
    typedef std::shared_ptr<VIOCreator>	IOCreator;
    typedef std::shared_ptr<VIOBoard>	IOBoard;
    typedef std::shared_ptr<VIOPort>	IOPort;
    typedef std::shared_ptr<VIOChannel>	IOChannel;
    
    // Erzeugen des Creator Objekts
    IOCreator creator = IOCreator (Object<VIOCreator> (boardName));
    if (creator != nullptr)
    {
    // Erzeugen des IOBoard Objekts
      xml.IntoElem ();
      IOBoard board = creator->ImportDigitalConf (boardID, xml);
      xml.OutOfElem ();
    }
    
    // Erzeugen eines PCL724 Port's
    IOPort port = IOPort (new TPCL724Port (portName, boardID, portID, ioMode, ioLogic));
    

    Es kompiliert, aber ist die Anwendung der shared_ptr auch richtig?

    Da ich 20 verschiedene I/O Karten und 3 verschiedene Motion Karten habe, also jede Menge code schreiben muss, will ich sicher sein, dass es dann auch funktioniert.

    Gruss
    Wale



  • Grundsätzliche Frage: Werden die Objekte im gleichen Projekt (keine dll, keine COM-Schnittstelle...) erzeugt? Wenn nein, wirst du vermutlich auch separate Funktionen für die Zerstörung in der Schnittstelle haben (dort wo alloziert wird, muss in der Regel auch die Freigabe erfolgen).

    Nehmen wir an das du den gesamten Code auf deiner Seite hast: shared_ptr brauchst du nur wenn du eine Referenzzählung brauchst, die Bibliothek selbst verwendet wohl nur rohe Zeiger. Wenn du die Objekte immer nur lokal erzeugst und freigibst ist shared_ptr die falsche Wahl aufgrund seines Overheads (dann eignet sich unique_ptr besser).



  • Und wenn dann doch shared_ptr benötigt werden, ist make_shared vorzuziehen.



  • Man kann auch einfach einen Pointer raus geben. Denn dann kann der Benutzer selbst entscheiden ob er diesen von einem Smartpointer verwalten lassen will.



  • Oder man gibt nen unique_ptr raus. Dann kann der User ebenfalls selbst entscheiden was er damit machen möchte, aber es leakt im Falle des Falles nixe.



  • Wenn nein, wirst du vermutlich auch separate Funktionen für die Zerstörung in der Schnittstelle haben.

    Ich dachte eigentlich, dass genau das durch die smart pointer automatisch erledigt wird.

    Für jeden pointer existieren die drei Funktionen. Damit es nicht zu lang wird
    habe ich die Schnittstelle auf ein Element gekürzt.

    typedef std::shared_ptr<VIOCreator> IOCreator;
    typedef std::shared_ptr<VIOBoard> IOBoard;
    typedef std::shared_ptr<VIOPort> IOPort;
    typedef std::shared_ptr<VIOChannel> IOChannel;
    
    typedef std::map<Tstring, IOCreator> IOCreators;
    typedef IOCreators::iterator IOCreator_It;
    
    class TInOut : public VModel
    {
    public:
    	TInOut	();
    	~TInOut	();
    
    	void AddIOCreator (const Tstring& boardID, IOCreator creator);
    	IOCreator Creator (const Tstring& boardID);
    	IOCreators GetIOCreators ();
    
    private:
    	IOCreators		mIOCreators;
    };
    

    Ich wollte die Pointer für die Port's und analog Kanäle zusätzlich auch in den Klassen für die einzelnen IO-Karten ablegen deshalb die Idee mit dem shared_ptr. Aber wenn das so schräg ist, kann ich darauf verzichten 🕶

    Ich werde es also mit dem unique_ptr probieren.

    Overhead und Geschwindigkeit sind hier aber komplett irrelevant. Der Aufbau des InOut Systems wird beim Start der Applikation gemacht, dabei wird auch die komplette Hardware initialisiert. Schon das initialisieren einer Schrittmotor Achse dauert länger als der Aufbau des InOut Systems und bei den meisten Maschinen sind mindestens 2 Achsen vorhanden. Die komplexeste Maschine hat 7 Achsen, da vergehen locker 2 Minuten bis die Maschine bereit ist.

    Der Applikations Programmierer hat nur die Wahl das InOut System zu benutzen, oder ein eigenes InOut System zu schreiben. Da wir nur zu zweit sind, sind Applikations Programmierer und Library Programmierer meist ein- und dieselbe Person 😉 Dadurch existiert dieses Problem nicht wirklich.

    Da das ganze System durch eine XML Datei konfiguriert wird, hat man auch genügend Möglichkeiten zur Anpassung an die Anforderungen.

    Unsere Programme steuern Produktionsmaschinen und werden im Idealfall genau einmal gestartet und laufen dann bis irgend eine Hardware aussteigt.



  • weicher schrieb:

    Wenn nein, wirst du vermutlich auch separate Funktionen für die Zerstörung in der Schnittstelle haben.

    Ich dachte eigentlich, dass genau das durch die smart pointer automatisch erledigt wird.

    Smartpointer können nicht raten, wie ein Objekt zerstört werden muss, wenn die Zerstörung "besonders" ablaufen muss. Wenn die Bibliothek als reiner Sourcecode eingebunden wird, liegt die Erstellung innerhalb deines Prozesses und du kannst Smartpointer nutzen, wenn du aber COM-Interfaces oder Klassen einer DLL nutzt, muss die Zerstörung über spezielle Routinen erfolgen und nicht über "delete".

    weicher schrieb:

    Ich wollte die Pointer für die Port's und analog Kanäle zusätzlich auch in den Klassen für die einzelnen IO-Karten ablegen deshalb die Idee mit dem shared_ptr. Aber wenn das so schräg ist, kann ich darauf verzichten 🕶

    Wer ist der "Besitzer"? Wenn der Zeigerbesitz unbestimmt ist (Im Sinne der letzte macht das Licht aus), ist ein shared_ptr richtig. Wenn aber definiert ist das der ursprüngliche Besitzer auch als letztes "stirbt" ist ein unique_ptr und die übergabe von regulären Zeigern an die anderen Elemente nicht verkehrt (und auch performanter).



  • Mit Overhead hat das ja auch nichts zu tun. Das Problem ist, das die Bibliothek und der Benutzer-Code nicht zwangsweise die selben Smartpointer-Implementierungen und nicht die selben C++-Runtimes (was somit das Memory-Management betrifft) haben.

    Du könntest nur dann fehlerfrei einen Shared-Pointer raus geben, wenn deine Bibliothek und das Benutzer-Programm mit dem selben Compiler und Runtime-Version kompiliert werden. Und auch die selbe Runtime-Instanz nutzen.



  • Ich habe jetzt mal versucht, anstelle von shared_ptr den unique_ptr zu benutzen. Das geht definitiv nicht ohne Komforteinbussen!

    Du könntest nur dann fehlerfrei einen Shared-Pointer raus geben, wenn deine Bibliothek und das Benutzer-Programm mit dem selben Compiler und Runtime-Version kompiliert werden.

    Das ist zu 100% gewährleistet. Wir schreiben Applikationen, Libraries und DLL's nur für unsere Firma.

    Auch die Maschinen laufen zu 100% in unserer Firma.

    Wenn der Zeigerbesitz unbestimmt ist (Im Sinne der letzte macht das Licht aus), ist ein shared_ptr richtig.

    Das ist genau das was ich will!



  • Default für Factory-Funktionen sollte grundsätzlich unique_ptr sein. shared_ptr nur wenn man weiss dass so-gut-wie jeder Benutzer der Factory-Funktion shared ownership brauchen wird. Weil man sich dann mit make_shared ein bisschen Overhead sparen kann.

    Man beachte auch dass unique_ptr R-Values (z.B. eben Returnwerte von Factory-Funktionen) implizit in shared_ptr konvertierbar sind. Umgekehrt allerdings nicht, 1x shared_ptr immer shared_ptr !



  • Wenn ich mich recht entsinne, wäre laut dem hier wäre unique_ptr trotzdem besser:

    https://youtu.be/uzF4u9KgUWI?t=2454

    Habs aber nie getestet.



  • Kann schon stimmen.
    Kommt halt auch auf den Anwendungsfall an. Und vermutlich auf die Implementierung.
    attribute((noinline)) ist z.B. oft gar keine so schlechte Idee 😉


Log in to reply