auto_ptr als Kennzeichnung der Objektverantwortung



  • Hallo zusammen,

    häufig ist es ja nicht leicht zu verdeutlichen, dass der Aufrufer
    für das zurückgelieferte Objekt verantwortlich ist und es löschen muss.

    Jetzt habe ich gerade in einem Beitrag von Nexus
    folgendes gesehen:

    std::auto_ptr<std::string> get_encryption_key();
    

    Haltet ihr sowas für stilistisch schön, oder verzichtet ihr eher drauf
    und macht Namenskonventionen wie get...() und create...()?

    Gruß,
    XSpille



  • Wenn eine Funktion ein Objekt erzeugt und zurückgibt (und dies nicht anderweitig verwaltet wird), dann fängt die bei mir meist mit create... an.

    Da du einen auto_ptr zurückgibst, ist die Situation sowieso klar, egal wie die Funktion nun heißt.



  • Athar schrieb:

    Da du einen auto_ptr zurückgibst, ist die Situation sowieso klar, egal wie die Funktion nun heißt.

    Meine Frage war eigentlich, ob ihr sowas zur Verdeutlichung verwedet. 😉 (EDIT: bzw. sollte sein)
    Also impliziere ich, dass es, wenn man einen auto_ptr verwendet, auch klar sein sollte.



  • Wenn sich die Frage auf auto_ptr bezog:
    Sicher! Damit muss sich der Aufrufer nicht mehr selbst um das Löschen kümmern, er kann aber, da man dem auto_ptr die Kontrolle über den Zeiger auch wieder "entreißen" kann.

    Edit: Den Typ schreibe ich meist auch in Headern nicht aus, stattdessen verwende ich typedefs mit einem dem Klassennamen vorangestelltem A (bzw. S bei shared_ptr).



  • XSpille schrieb:

    Jetzt habe ich gerade in einem Beitrag von Nexus
    folgendes gesehen:

    std::auto_ptr<std::string> get_encryption_key();
    

    Haltet ihr sowas für stilistisch schön, oder verzichtet ihr eher drauf
    und macht Namenskonventionen wie get...() und create...()?

    In dem konkreten Beispiel hätte ich den String wohl by value geliefert. Der hat schließlich schon selbst eine Speicherverwaltung.

    Grundsätzlich aber finde ich die Rückgabe eines auto_ptr besser/schöner, als per Namenskonvention festzulegen, dass z.B. das Präfix "create" bedeutet, dass der Aufrufer für den Speicher verantwortlich ist. Bei Verwendung eines auto_ptr wird das Objekt garantiert entsorgt, bei Namenskonventionen gibt es zu viele Fehlermöglichkeiten.



  • Ich gebe von meinen create_*()-Funktionen eigentlich immer Smartpointer zurück, wenn ich dem Nutzer schon den Genuss eines stinknormalen Stack-Konstruktoraufruf nicht bieten kann.



  • Ich verwende new/delete sehr selten. Im aktuellen Projekt gibt es eine Stelle, an der ich es nutze. Eine "AbstractFactory" liefert mir da einen auto_ptr<ABC> zurück. Das habe ich auch aus dem Grund gemacht, damit es klar ist, wie die "Besitzverhältnisse" aussehen.

    Ich freue mich schon auf die C++0x Features. Mit "move semantics" sollte "pass-by-value" populärer werden. Außerdem kann man auto_ptr durch den sichereren und flexibleren unique_ptr ersetzen.

    kk



  • XSpille schrieb:

    Haltet ihr sowas für stilistisch schön, oder verzichtet ihr eher drauf
    und macht Namenskonventionen wie get...() und create...()?

    Das war mehr ein Beispiel, genau so habe ich std::auto_ptr eigentlich noch nie verwendet. Gerade für Getter gebe ich oft Werte (bei skalaren Typen und kleineren Klassen) bzw. Const-Referenzen (bei grösseren Klassen wie std::string ) zurück, hierbei ist ja auch keine Move-Semantik erwünscht. Für Factory-Methoden scheint mir std::auto_ptr aber ein durchaus gangbarer Weg zu sein. Jedoch würde ich nicht behaupten, besitzende rohe Zeiger zurückzugeben sei generell falsch. Wenn das API gut dokumentiert ist, kann man z.B. für CreateXY() ein DestroyXY() -Äquivalent anbieten. Allerdings sind Smart-Pointer oft einfach praktisch.

    Grautvornix schrieb:

    In dem konkreten Beispiel hätte ich den String wohl by value geliefert. Der hat schließlich schon selbst eine Speicherverwaltung.

    Im anderen Thread habe ich den Vorschlag für den Fall gebracht, dass RVO nicht funktioniere und die Kopie ein tatsächliches Geschwindigkeitsproblem sei. Ich habe da auch die Wert-Rückgabe favorisisert.

    Grautvornix schrieb:

    Grundsätzlich aber finde ich die Rückgabe eines auto_ptr besser/schöner, als per Namenskonvention festzulegen, dass z.B. das Präfix "create" bedeutet, dass der Aufrufer für den Speicher verantwortlich ist. Bei Verwendung eines auto_ptr wird das Objekt garantiert entsorgt, bei Namenskonventionen gibt es zu viele Fehlermöglichkeiten.

    Da stimme ich zum Teil zu. Ein wichtiger Vorteil der Smart-Pointer besteht sicherlich darin, dass man ihnen die Besitzverhältnisse gut ansieht. Dadurch kann man damit Schnittstellen implizit aussagekräftiger machen. Andererseits schränkt man den Benutzer auch ein, beispielsweise geht die Möglichkeit kovarianter Rückgabewert verloren. Bei shared_ptr verliert man zudem die Freiheit, auf einen anderen Smart-Pointer umzusteigen, während es auto_ptr::release() einfacher macht. Ich habe allerdings schon ab und zu shared_ptr in Code gesehen, wo ein anderer Smart-Pointer (oft scoped_ptr oder auto_ptr ) sich besser eignen würde.

    Allerdings stören mich zwei Dinge an std::auto_ptr , die durch eine eigene Implementierung im momentanen Standard behoben werden könnten, im Gegensatz zur von krümelkacker angesprochenen Verbesserung durch std::unique_ptr . Erstens ist keine Methode vorhanden, um den Zeiger auf Validität zu prüfen (z.B. Konvertierungsoperator zu bool -ähnlichem Typ). Dies ist zwar über die Methode get() möglich, allerdings ist deren Aufgabe primär, den internen Zeiger zurückzugeben. Ein Stück weit verletzt man dadurch auch die Kapselung und drückt explizit aus, dass man am konkreten Wert des Zeigers interessiert ist, und nicht nur an der Gleichheit zu NULL . Zweitens ist der momentane std::auto_ptr potentiell unsicher in der Verwendung: Wenn der Pointee-Typ T bei der auto_ptr<T> -Deklaration nicht vollständig definiert ist, führt der Destruktor auto_ptr::~auto_ptr() zu undefiniertem Verhalten, da das Objekt ohne zerstört zu sein freigegeben wird. Hingegen würden Smart-Pointer, die mit unvollständig definierten Typen umgehen können, gerade dabei helfen, Projekt-Abhängigkeiten und damit die Kompilierzeit zu verringern.



  • Nexus schrieb:

    Allerdings stören mich zwei Dinge an std::auto_ptr , die durch eine eigene Implementierung im momentanen Standard behoben werden könnten, im Gegensatz zur von krümelkacker angesprochenen Verbesserung durch std::unique_ptr .

    Was genau meinst Du? Dass auto_ptr noch verbessert werden soll? Wozu? auto_ptr ist zugunsten von unique_ptr als "deprecated" gekennzeichnet worden.

    Nexus schrieb:

    Erstens ist keine Methode vorhanden, um den Zeiger auf Validität zu prüfen (z.B. Konvertierungsoperator zu bool -ähnlichem Typ).

    std::unique_ptr bietet dafür "explicit operator bool() const" an.

    Nexus schrieb:

    Zweitens ist der momentane std::auto_ptr potentiell unsicher in der Verwendung: Wenn der Pointee-Typ T bei der auto_ptr<T> -Deklaration nicht vollständig definiert ist, führt der Destruktor auto_ptr::~auto_ptr() zu undefiniertem Verhalten, da das Objekt ohne zerstört zu sein freigegeben wird. Hingegen würden Smart-Pointer, die mit unvollständig definierten Typen umgehen können, gerade dabei helfen, Projekt-Abhängigkeiten und damit die Kompilierzeit zu verringern.

    Bei unique_ptr gibt es ja dafür die Parametrisierung des "Deleters":

    class foo;
    
    struct foo_deleter {
      void operator()(foo*) const; // irgendwo anders definiert,
                                 // wo foo komplett bekannt ist
    };
    
    typedef std::unique_ptr<foo,foo_deleter> unique_foo_ptr;
    

    Schade ist jedoch, dass man hier keine Elementfunktionen deklarieren kann, ohne verraten zu müssen, wie foo aufgebaut ist. Das muss man irgendwie umgehen, wenn man die Abhängigkeiten reduzieren will.

    kk



  • krümelkacker schrieb:

    Was genau meinst Du? Dass auto_ptr noch verbessert werden soll? Wozu? auto_ptr ist zugunsten von unique_ptr als "deprecated" gekennzeichnet worden.

    Ich meinte mehr, dass man mit C++98-Mitteln bereits Dinge verbessern kann. Also falls man sich selbst einen Smart-Pointer mit Move-Semantik baut und vielleicht noch nicht bald auf C++0x umsteigen kann.

    Die beiden entsprechenden Neuerungen in unique_ptr waren mir aber in der Tat unbekannt, vielen Dank für die Erläuterung.


Log in to reply