Frage zur Benutzung von new und delete bei einem Beispiel


  • Mod

    versionsnummer schrieb:

    SeppJ schrieb:

    Weil das denkbar schlechter Code ist. Nicht abgucken!

    Faustregel: Wenn new oder delete direkt im Code auftauchen, ist der Code höchstwahrscheinlich nicht gut...

    ... oder von jemandem, der sämtliche Sprachmittel genau kennt und weiß, in welcher Situation man welche am besten anwendet statt nur nachzuplappern, was gerade in Mode ist.

    Nur wenn Schweine fliegen können. Ob ein bestimmter Code gut oder schlecht ist, hat jedenfalls nichts damit zu tun, wer ihn geschrieben hat.
    Andererseits lässt dein überflüssiger Kommentar sehr wohl auf deine Disposition schliessen.



  • camper schrieb:

    versionsnummer schrieb:

    SeppJ schrieb:

    Weil das denkbar schlechter Code ist. Nicht abgucken!

    Faustregel: Wenn new oder delete direkt im Code auftauchen, ist der Code höchstwahrscheinlich nicht gut...

    ... oder von jemandem, der sämtliche Sprachmittel genau kennt und weiß, in welcher Situation man welche am besten anwendet statt nur nachzuplappern, was gerade in Mode ist.

    Nur wenn Schweine fliegen können. Ob ein bestimmter Code gut oder schlecht ist, hat jedenfalls nichts damit zu tun, wer ihn geschrieben hat.
    Andererseits lässt dein überflüssiger Kommentar sehr wohl auf deine Disposition schliessen.

    Lustigerweise habe ich gerade in einem Code-Vorschlag in einem anderen Thread ein noch unsäglicheres " new std::shared_ptr<> " verwendet,
    das sich in diesem speziellen Fall durchaus nachvollziehbar rechtfertigen lässt 😮

    Die Faustregel, möglichst kein new/delete zu verwenden hat allerdings nichts mit "Mode" zu tun, sondern führt fast immer zu simplerem und weniger
    fehleranfälligem Code. Ich schliesse mich hier also der new/delete -ist-baba-Fraktion an, auch wenn ich einräume, dass es seltene Fälle gibt, wo sie Sinn machen können.

    Finnegan


  • Mod

    Finnegan schrieb:

    auch wenn ich einräume, dass es seltene Fälle gibt, wo sie Sinn machen können.

    Welche Fälle?



  • SeppJ schrieb:

    Finnegan schrieb:

    auch wenn ich einräume, dass es seltene Fälle gibt, wo sie Sinn machen können.

    Welche Fälle?

    Beispielsweise die seltenen Fälle in denen man das Ownership eines Objektes durch ein Interface durchreichen möchte, das keine Smart Pointer versteht -
    z.B. das Shared Ownership eines std::shared_ptr duch ein void* -Interface einer C-API, wo der Shared Pointer dann beispielsweise in einer Callback-Funktion
    freigegeben werden soll.

    Oder wie in dem Beispiel im verlinkten Thread, wo der lockfree-queue nur mit trivialen Datentypen umgehen kann (also keine Smart Pointer im queue, lediglich nackte Pointer),
    in diesem aber dennoch Shared Ownership verwaltet werden soll, damit sigergestellt ist dass das referenzierte Objekt so lange existiert wie noch ein Thread darauf zugreifen könnte.
    Es wird sicher noch andere Beispiele geben, aber wie gesagt, das werden wohl alles Ausnahmefälle sein.

    Ich gebe allerdings zu, dass man auch in den oben genannten Fällen nicht zwangsweise ein new benötigt - ein std::make_unique<T>().release() täte es natürlich auch -
    allerdings macht das bei simplen internen Objekten, deren Konstruktor nicht wirft keinen Unterschied.

    Finnegan


  • Mod

    Finnegan schrieb:

    Beispielsweise die seltenen Fälle in denen man das Ownership eines Objektes durch ein Interface durchreichen möchte, das keine Smart Pointer versteht -
    z.B. das Shared Ownership eines std::shared_ptr duch ein void* -Interface einer C-API, wo der Shared Pointer dann beispielsweise in einer Callback-Funktion
    freigegeben werden soll.

    Uns ist ein unmodern verfasstes Interface gegeben - natürlich macht das den es verwendenden Code entsprechend unmodern. Wenn wir RAII von vornherein konsequent durchgesetzt hätten, wäre auch niemals ein new notwendig gewesen. Genau wie const_cast in modernem C++ prinzipiell keine Daseinsberechtigung hat - nur weil bestimmte legacy APIs einfach keine const correctness haben (außer man implementiert irgend eine Methode zweimal und möchte sich Tipparbeit sparen 😛 ).

    Außerdem sollte nicht einfach new verwendet werden, weil irgendwelche Parameter rohe Zeiger sind.

    foo(new A, new B); // Kann lecken
    
    auto a = std::make_unique<A>();
    auto b = std::make_unique<B>();
    foo(a.release(), b.release()); // Leidet nicht unter dem selben Problem.
    

    Im Übrigen sehe ich nicht ein, weshalb Faustregeln wie diese nicht dogmatisiert werden sollen. Wir hatten genug Neulinge, die sich "passt schon, funzt ja" einreden und dann mit völlig schrottem Code antanzen. Lieber einfach kategorisch ablehnen, alle Ausnahmen sind so spezialisiert, dass sie im Falle des Falles auch hier diskutiert werden können.



  • Arcoth schrieb:

    Uns ist ein unmodern verfasstes Interface gegeben - natürlich macht das den es verwendenden Code entsprechend unmodern. Wenn wir RAII von vornherein konsequent durchgesetzt hätten, wäre auch niemals ein new notwendig gewesen.

    Ich bin mir da nicht so sicher. Wie implementiert man denn ohne new so etwas wie make_unique oder sogar std::vector wo ein reserve() zwar
    den Speicher für die Objekte reserviert, diese aber erst später konstruiert werden? Ich denke zumindest ein placement new wird benötigt -
    oder vielleicht die Möglichkeit den Konstruktor für ein nicht initalisiertes Objekt manuell aufzurufen 😉

    Arcoth schrieb:

    Im Übrigen sehe ich nicht ein, weshalb Faustregeln wie diese nicht dogmatisiert werden sollen. Wir hatten genug Neulinge, die sich "passt schon, funzt ja" einreden und dann mit völlig schrottem Code antanzen. Lieber einfach kategorisch ablehnen, alle Ausnahmen sind so spezialisiert, dass sie im Falle des Falles auch hier diskutiert werden können.

    Nur zur Erinnerung, ich bin nicht derjenige der sich gegen die Faustregeln auflehnt, im Gegenteil, ich hab sie sogar verteidigt (! s.o.).
    Ich hatte nicht vor hier eine Diskussion vom Zaun zu brechen, bei der ich eh auf eurer Seite bin, sondern es ging mir lediglich darum auf ein Beispiel zu verweisen,
    wo ichs zufällig gerade eben erst verwendet hatte. Dort wird via new lediglich der ( noexcept ) Copy Constructor von shared_ptr in einer privaten Methode aufgerufen,
    was man meines erachtens durchas auch mal mit new machen darf, wenn der Code dadurch kompakter wird. Nicht jeder Code ist so generisch dass man mit allem rechnen muss 😉

    Finnegan



  • hi,
    ich möchte mich hiermit bei allen für die Hilfe bedanken. 👍



  • Arcoth schrieb:

    [code="cpp"]
    foo(new A, new B); // Kann lecken

    auto a = std::make_unique<A>();
    auto b = std::make_unique<B>();
    foo(a.release(), b.release()); // Leidet nicht unter dem selben Problem.[/code

    Wo werden denn hier die Ressourcen freigegeben?
    [code="cpp"]class A
    {
    public:
    A::A(){cout << "Constructor\n";}
    A::~A(){cout << "Destructor\n";}
    };

    void foo(A *ptr)
    {
    cout << "Foo\n";
    }

    int main()
    {
    foo(make_unique<A>().release());
    }[/code]
    erzeugt bei mir die Ausgabe:
    Constructor
    Foo 😕


  • Mod

    Finnegan schrieb:

    Arcoth schrieb:

    Uns ist ein unmodern verfasstes Interface gegeben - natürlich macht das den es verwendenden Code entsprechend unmodern. Wenn wir RAII von vornherein konsequent durchgesetzt hätten, wäre auch niemals ein new notwendig gewesen.

    Ich bin mir da nicht so sicher. Wie implementiert man denn ohne new so etwas wie make_unique oder sogar std::vector wo ein reserve() zwar
    den Speicher für die Objekte reserviert, diese aber erst später konstruiert werden? Ich denke zumindest ein placement new wird benötigt -
    oder vielleicht die Möglichkeit den Konstruktor für ein nicht initalisiertes Objekt manuell aufzurufen 😉

    Danke für den Nitpick, aber ich rede hier natürlich von Code außerhalb der Standardbibliothek. 😉

    @Belli: Die Idee war, dass foo Besitz der Objekte ergreift.



  • Ach soooo ...

    foo(new A, new B); // Kann lecken
    

    lecken also für den Fall, dass beim zweiten Konstruktoaufruf was schief geht?!
    So weit hatte ich ursprünglich nicht gedacht ...
    Denn Besitz übernehmen könnte (bzw. müsste) foo schließlich auch so?!



  • ja, und du kannst, wenn es ein funktionsaufruf ist, nicht einmal garantieren, in welcher reihenfolge A und B konstruiert werden. wenn B::B fehlschlägt und nach A::A aufgerufen wird, dann gibt es keine möglichkeit mehr, das A-objekt freizugeben; foo wird auch niemals aufgerufen.



  • Hallo Arcoth,

    Arcoth schrieb:

    class Mammal 
    { 
    public: 
        virtual ~Mammal() =0;   // pure abstract; Instanz von Mammal ist nicht möglich! 
        virtual void Speak() const { std::cout << "???" << std::endl; } // Default 
    }; 
    inline Mammal::~Mammal() {}
    

    Warum nicht

    class Mammal 
    { 
    public: 
        virtual ~Mammal() = default;
        virtual void Speak() const = 0;
    };
    

    zu Speak: aus dem Beispielcode des TE ist meines Erachtens ersichtlich, dass es eine Default-Implementierung für die Methode Speak gibt. Das wollte ich dann genau so machen.
    zu ~Mammal: siehe Scott Meyers 'Effektiv C++' Item 14 "eine Basisklasse soll einen virtuellen Destruktor haben" und noch ein Item (wahrscheinlich aus 'Mehr Effektiv C++'), was ich jetzt nicht wieder finde, was besagt: "Mache Dir keine Instanzen von Basisklassen". Letzeres kann ich aus eigener (schmerzlicher!) Erfahrung bestätigen.

    Gruß
    Werner


Anmelden zum Antworten