Heap und Stack



  • Hallo. Danke auch für deine Antwort.

    Im Prinzip arbeite ich schon sehr viel mit Containern. Mein Problem bei den ganzen Standard-Konstrukten wie den stl-containern ist, dass sie mitunter so einfach zu benutzen sind, dass man sich (ich mir) keine Gedanken mehr darüber machen muss, was intern eigentlich passiert. Das ist theoretisch der große Vorteil. Mir ist es jetzt aber scheinbar zum Verhängnis geworden, da ich irgendwie das Gefühl hatte, damit zuviel "Overhead" zu erzeugen und mit nativeren Konstrukten wie "new" besser (performanter) fahre, als Sachen zu nutzen, die ihre Arbeitsweise vor mir "verbergen". Das ist auch der Grund, warum ich dringend dazulernen möchte, damit mir solche "Fehler" in Zukunft nicht mehr zum Verhängnis werden. Daher bin ich sehr dankbar über solche Lesetipps und werde auch ein bisschen hier im Forum weiter stöbern.

    Ich möchte aber doch nochmal kurz mein Problem vorstellen, ich wills aber jetzt nicht ausarten lassen. So wie im Beispiel sieht ungefähr meine Klasse aus. SehrGrosseKlasse wird irgendwo außerhalb von meinem "Zuständigkeitsbereich" mit new erzeugt und dann läuft das Teil so vor sich hin (bitte nicht über den Inhalt der main() schimpfen).

    Die Sache sieht so aus, dass SehrGrosseKlasse viele Objekte erzeugt, unter anderem auch welche, die auf andere Member von SehrGrosseKlasse zugreifen müssen.

    Beispiel:

    class SehrGrosseKlasse {
    
    public:
       SehrGrosseKlasse()
       : m_ressource1(),
         m_ressource2(),
         m_ressource3(),
         m_MeineAndereKlasse_1(NULL),
         m_MeineAndereKlasse_2(NULL),
         m_MeineAndereKlasse_3(NULL),
         ...
       {
          m_MeineAndereKlasse_1 = new AndereKlasse(m_ressource1, m_ressource2, m_ressource3);
          m_MeineAndereKlasse_2 = new AndereKlasse(m_ressource1, m_ressource2, m_ressource3);
          m_MeineAndereKlasse_3 = new AndereKlasse(m_ressource1, m_ressource2, m_ressource3);
       }
    
       ~SehrGrosseKlasse() { // delete von meinen anderen Klassen. };
    
    private:
    
    Ressource m_ressource1;
    Ressource m_ressource2;
    Ressource m_ressource3;
    
    AndereKlasse * m_MeineAndereKlasse_1;
    AndereKlasse * m_MeineAndereKlasse_2;
    AndereKlasse * m_MeineAndereKlasse_3;
    
    };
    
    class AndereKlasse {
    
    public:
       AndereKlasse(Ressource & ressource1, Ressource & ressource2, Ressource & ressource3) 
       : m_ressource1(ressource1),
         m_ressource2(ressource2),
         m_ressource3(ressource3)
       {}
    
    private:
       Ressource & m_ressource1;
       Ressource & m_ressource2;
       Ressource & m_ressource3;
    }
    };
    
    int main()
    {
       SehrGrosseKlasse * klasse = new SehrGrosseKlasse();
       while(1);
    }
    

    Mit std::vector als Container für AndereKlasse sieht das schon eleganter aus (siehe Beispiel 2). Und wie ich gelernt habe, wird vector vermutlich den Inhalt auch auf den Heap packen und ich muss mir darum keine Gedanken machen. Worum ich mir aber bisher Gedanken gemacht habe ist die Tatsache, dass ich nun ein lokales Objekt der Klasse AndereKlasse erzeuge und push_back das irgendwohin kopiert. Wenn jetzt AndereKlasse auch groß ist und da fleißig hin und her kopiert wird, ist das doch auch nicht die beste Lösung. Was denkt ihr?

    Alternative wäre noch Variante 1 mit SmartPointern.. m_MeineAndereKlasse_{1,2,3} direkt in der Initializerlist des Konstruktors von SehrGrosseKlasse mit den Ressourcen zu initialisieren ist sicher auch noch eine Variante (?), allerdings sieht das ganze sehr viel komplexer aus, als hier dargestellt, deshalb ist das eigentlich keine Option.

    Beispiel 2

    class SehrGrosseKlasse {
    
    public:
       SehrGrosseKlasse()
       : m_ressource1(),
         m_ressource2(),
         m_ressource3(),
         m_MeineAndereKlasse(),
         ...
       {
          m_MeineAndereKlasse.push_back(AndereKlasse(m_ressource1, m_ressource2, m_ressource3));
          m_MeineAndereKlasse.push_back(AndereKlasse(m_ressource1, m_ressource2, m_ressource3));
          m_MeineAndereKlasse.push_back(AndereKlasse(m_ressource1, m_ressource2, m_ressource3));
       }
    
    private:
    
    Ressource m_ressource1;
    Ressource m_ressource2;
    Ressource m_ressource3;
    
    std::vector< AndereKlasse > m_MeineAndereKlasse;
    
    };
    

    Danke 🙂



  • cole-hawk schrieb:

    ..., da ich irgendwie das Gefühl hatte, damit zuviel "Overhead" zu erzeugen und mit nativeren Konstrukten wie "new" besser (performanter) fahre, als Sachen zu nutzen, die ihre Arbeitsweise vor mir "verbergen".

    Wenn du solchen Wert auf Performance legst, dann solltest du vielleicht wissen, dass new bzw. die meisten Varianten von malloc auf denen das new letztendlich basiert, eine relativ teure Operation sind, die höchstwahrscheinlich jeglichen eventuellen Overhead, den du z.B. durch die Verwendung eines std::unique_ptr / std::make_unique an dieser Stelle haben wirst, vollkommen dominieren werden (allerdings wage ich zu behaupten, dass die gängigen Implementierungen von std::unique_ptr / std::make_unique derart leichtgewichtig sind, dass sie überhaupt keinen Overhead im Vergleich zu einem new / delete -Paar haben).

    Aus Performance-Betrachtungen her wäre es bei deinen Code-Beispielen daher sinnvoller zu überlegen, ob man nicht auf das eine oder andere new / make_unique komplett verzichten kann. Spricht z.B. etwas dagenen, die Instanzen von AndereKlasse nicht als Pointer vorzuhalten, sodern diese direkt als Member von SehrGrosseKlasse zu haben?:

    class SehrGrosseKlasse {
    ...
    AndereKlasse m_MeineAndereKlasse_1;
    AndereKlasse m_MeineAndereKlasse_2;
    AndereKlasse m_MeineAndereKlasse_3;
    };
    

    Das hätte performance-technisch zwei Vorteile:

    1. Instanzen von SehrGrosseKlasse können mit nur einer einzigen Speicher-Allokation erzeugt werden.

    2. Die Member von SehrGrosseKlasse ( m_ressource1 , m_MeineAndereKlasse_1 ) liegen im Speicher direkt hintereinander, d.h. wenn du z.B. (was ich für wahrscheinlich halte) in deinem Code nacheinander auf
    m_MeineAndereKlasse_1 , m_MeineAndereKlasse_1 , etc. zugreifst, kann dein Programm so besser vom CPU-Cache profitieren - ein Effekt der ebenfalls jeglichen eventuellen (wenn überhaupt) new / delete -Performancevorteil vollkommen dominieren wird. Sich an Pointern "entlangzuhangeln" ist oft Gift für die Performance: Um so einen Pointer aus dem Speicher zu laden, liest so eine CPU üblicherweise eine ganze Cache-Line aus dem Speicher (bei gängigen x86/x64-CPUs sind das 64 Bytes) und benötigt letztendlich nur 4-8 Bytes davon (wenn die gelesenen 64 Bytes nicht sogar noch andere Speicherbereiche aus dem Cache verdrängen, die man im Anschluss ohnehin wieder benötigt, also dann nochmal gelesen werden müssen).

    Generell würde ich also erst einmal immer bevorzugen, die AndereKlasse zu einem direkten Member von SehrGrosseKlasse zu machen, es sei denn du hast einen guten Grund dafür, es anders zu machen - z.B. wenn die AndereKlasse -Member nicht in einer 1:1-Beziehung zu SehrGrosseKlasse stehen. In diesem Fall machen dann eventuell std::unique_ptr / std::shared_ptr Sinn, je nachdem welche Form von "Ownership" gewünscht, bzw. sinnvoll ist. Allerdings kann man einer anderen Klasseninstanz durchaus auch einen &m_MeineAndereKlasse_1 -Pointer mitgeben, wenn garantiert ist (am besten durch Code-Design), dass diese nicht länger lebt als SehrGrosseKlasse 🙂

    Gruss,
    Finnegan



  • Spricht z.B. etwas dagenen, die Instanzen von AndereKlasse nicht als Pointer vorzuhalten, sodern diese direkt als Member von SehrGrosseKlasse zu haben?

    Also auf den Rat meines Kollegen hin, habe ich die Verwendung von dieser Art der Instanziierung weitestgehend reduziert und tatsächlich als Member angelegt. Da kam dann nur eben die Eingangsfrage auf, wo die ganzen Member landen (heap oder stack), wenn SehrGrosseKlasse per new instanziiert wird (da ich die Member ja nun nicht mehr mit new instanziiere). Aber so wie ich das verstanden habe, landet alles im Heap. Macht ja auch Sinn, hatte mich trotzdem beschäftigt.

    Was ich aber bei meinem Beispiel vergessen habe zu erwähnen ist, das ich verschiedene Fälle habe, bei denen mir das "Problem" über den Weg gelaufen ist. Und einer der Gründe, der gegen das "direkt als Member" vorhalten spricht ist, dass das Erzeugen mitunter abhängig von Vorbedingungen ist. Das habe ich in dem Codebeispiel weggelassen.. Ganz simpel z.B. sowas in der Art:

    bool initOk = m_ressource1.init();
    if ( initOk ) 
       m_MeineAndereKlasse_1 = new AndereKlasse(m_ressource1, m_ressource2, m_ressource3);
    

    Wenn AndereKlasse etwas größer ist, was sie eben ist, dann ist es sicher kein guter Weg das Teil trotzdem zu erzeugen und dann nicht zu verwenden...

    Würde das mit make_unique dann so aussehen?

    std::unique_ptr< AndereKlasse > m_MeineAndereKlasse_1;
    m_MeineAndereKlasse_1 = std::make_unique< AndereKlasse(m_ressource1, m_ressource2, m_ressource3) >();
    

    So wie ich das sehe wird hiermit auch nur wieder das new verwendet.
    Das heißt also für mich, mein Meister wollte mir damit sagen: "new allein ist böse, da du garantiert das delete vergessen wirst.", aber nicht "new und heap sind etwas böses". Letzteres war nämlich mein Gedanke bei seiner Aussage und das stand im Widerspruch mit dem, was ich bisher so über Heap & Stack gelesen habe.

    Ich nehme also mit: RAII ist etwas gutes und Smart Pointer beispielsweise setzen dieses Konzept um.



  • cole-hawk schrieb:

    Würde das mit make_unique dann so aussehen?

    Nein, der Template Parameter ist nur der Name der Klasse. Außerdem bietet sich die Verwendung von auto an, dann muss man sich nicht wiederholen:

    auto m_MeineAndereKlasse_1 = std::make_unique<AndereKlasse>(m_ressource1, m_ressource2, m_ressource3);
    

    Oder soll das Beispiel für einen Membervariable sein? Da geht das natürlich nicht mit auto.



  • cole-hawk schrieb:

    bool initOk = m_ressource1.init();
    if ( initOk ) 
       m_MeineAndereKlasse_1 = new AndereKlasse(m_ressource1, m_ressource2, m_ressource3);
    

    Sowas ist durchaus ein guter Grund die "anderen" als Pointer vorzuhalten.

    cole-hawk schrieb:

    Wenn AndereKlasse etwas größer ist, was sie eben ist, dann ist es sicher kein guter Weg das Teil trotzdem zu erzeugen und dann nicht zu verwenden...

    Nein. Wenn der Member auch "nicht gesetzt" sein kann, halte ich es auch erstmal für einen guten Weg das über einen Pointer zu lösen, der eben auch eine Repräsentation für "nicht gesetzt" hat ( nullptr ). Es kann sein, dass man sich manchmal die Datenstruktur möglichst flach halten möchte (Cache oder andere Gründe, weshalb alles möglichst kompakt im Speicher liegen sollte), und es über eine spezielle "nicht gesetzt"-Instanz löst, aber dafür sollte man zuerst einen wirklich triftigen Grund haben.

    cole-hawk schrieb:

    Würde das mit make_unique dann so aussehen?

    Siehe Antwort von sebi707.

    cole-hawk schrieb:

    So wie ich das sehe wird hiermit auch nur wieder das new verwendet.
    Das heißt also für mich, mein Meister wollte mir damit sagen: "new allein ist böse, da du garantiert das delete vergessen wirst.", aber nicht "new und heap sind etwas böses". Letzteres war nämlich mein Gedanke bei seiner Aussage und das stand im Widerspruch mit dem, was ich bisher so über Heap & Stack gelesen habe.

    Nein, der Heap ist nix böses, das ist am Ende auch nur Speicher. Der Stack hat allerdings den Vorteil, dass Speicher-Allokationen dort quasi "umsonst" sind (Stack-Pointer hochzählen), und weil der Stack-Speicher von laufenden Programmen meisst ziemlich gut beackert wird, man bei Daten, die dort liegen wahrscheinlich eher von CPU-Cache-Effekten profitieren kann.
    Und ja, der vornehmliche Grund, weshalb man unique_ptr und Konsorten nehmen sollte ist, dass man das delete einfach nicht vergessen kann, selbst wenn die Code-Ausführung ungeahnte Wege nimmt (Exceptions et al.)... und natürlich dass man keine "Dangling Pointer" hat die doch irgendwie noch stillschweigend funktionieren (nach dem Reset ist das Ding einfach nullptr , das läuft garantiert vor die Wand).

    cole-hawk schrieb:

    Ich nehme also mit: RAII ist etwas gutes und Smart Pointer beispielsweise setzen dieses Konzept um.

    Wenn man mich auf eine einsame Insel verbannen würde, wo ich nur in C programmieren darf, aber ich mir ein einziges Feature von C++ aussuchen kann, dann wären es wahrscheinlich Destruktoren und die schließende geschweifte Klammer } ... obwohl ich die anderen Features auch vermissen würde 😃

    Finnegan



  • SeppJ schrieb:

    Der Ratschlag an jemanden auf deinen Kenntnisstand sollte wohl eher sogar noch radikaler sein: Benutze niemals new!

    new hat seine Anwendungen, wie alle anderen Sprachelemente auch. new- und raw pointer-Bashing ist im Moment scheinbar in Mode, vermutlich wegen der smart pointers von C++11 ff., die natürlich auch ihre Anwendungen haben. Deshalb braucht man aber nicht den Eindruck vermitteln, raw pointers und new seien per se überflüssig.


  • Mod

    Dann nenn mal einen einzigen Fall, new zusammen mit einem rohen Zeiger zu benutzen, bei dem die Alternativen nicht in jeder Hinsicht besser sind.



  • eine DLL mit Funktionen zur E/A und Verarbeitung von asynchron eintreffenden Datenpaketen, die als Objekte verwaltet werden. Zu jedem Zeitpunkt ist null oder ein Datenpaket-Objekt ausgewählt und dazu an einen globalen Zeiger gebunden; in letzterem Fall beziehen sich die folgenden Funktionsaufrufe auf das ausgewählte Objekt. Zeitpunkt und Reihenfolge des Eintreffens der Datenpakete ist ebensowenig vorhersehbar wie die Festlegung des aktuell ausgewählten Pakets; Zeitpunkt, Art und Argumente der Funktionsaufrufe zur E/A und Verarbeitung der Datenpakete.

    weitere Anwendungen:

    Interfacing mit C;
    Objekte mit genau bekannter Lebensdauer und mehreren Benutzern;
    legacy code;

    Beispiele und Übungsaufgaben aus Textbüchern lassen sich nicht immer 1:1 auf die Praxis bei gegebenen Randbedingungen in Industrie und Lehre übertragen.



  • großbuchstaben schrieb:

    eine DLL mit Funktionen zur E/A und Verarbeitung von asynchron eintreffenden Datenpaketen, die als Objekte verwaltet werden. Zu jedem Zeitpunkt ist null oder ein Datenpaket-Objekt ausgewählt und dazu an einen globalen Zeiger gebunden; in letzterem Fall beziehen sich die folgenden Funktionsaufrufe auf das ausgewählte Objekt. Zeitpunkt und Reihenfolge des Eintreffens der Datenpakete ist ebensowenig vorhersehbar wie die Festlegung des aktuell ausgewählten Pakets; Zeitpunkt, Art und Argumente der Funktionsaufrufe zur E/A und Verarbeitung der Datenpakete.

    Ich muss zugeben, dass ich mit deinem Beispiel nichts anzufangen weiss. Wichtig ist für mich: Wem gehört das Objekt? (sprich: Wer gibt es wieder frei?). Nackte Zeiger an sich sind völlig in Ordnung, die verwende ich auch massenhaft. Allerdings ist keiner davon ein "besitzender" Zeiger, der irgendwann ein delete oder free oder ähnliches benötigt. D.h. es existiert irgendwo im Programm ein unique_ptr / shared_ptr oder eine Member/Stack-Variable von dem der Pointer via addressof-Operator geholt wurde, die der eigentliche Besitzer der Objektinstanz ist, und sicherstellt, dass der Pointer mindestens so lange gültig ist, wie darauf zugegriffen wird.

    Kommt der Pointer aus der DLL, dann ist es entweder nicht meine Aufgabe, das Objekt wieder freizugeben, oder aber ich muss eine destroyObject() -Funktion oder ähnliches aufrufen. Im letzteren Fall packe ich den Pointer üblicherweise in einen Smart Pointer mit speziellem Deleter, der destroyObject() automatisch aufruft.

    großbuchstaben schrieb:

    weitere Anwendungen:

    Interfacing mit C;

    Lustigerweise arbeite ich gerade an einem Projekt, bei dem ich intensiv mit C-Bibliotheken zu tun habe, und ich muss sagen, dass ich dabei bisher sehr gute Erfahrungen mit unique_ptr / shared_ptr oder aber auch Microsoft::WRL:ComPtr (Teile des Projekts verwenden unter Windows DirectX) gemacht habe.

    Eine der verwendeten Bibliotheken nutzt z.B. intensiv GLib/GObject, bei denen man oft Objekte in die Hand bekommt, bei denen man sich selbst darum kümmern muss, deren Referenzzähler zu dekrementieren, wenn man damit fertig ist. Ich muss sagen, dass gerade der unique_ptr in diesen Fällen eine ziemliche Erleichterung ist:

    struct GObjectDeleter
    {
        inline void operator()(gpointer object)
        {
            assert(G_IS_OBJECT(object));
            g_object_unref(object);
        }
    };
    
    std::unique_ptr<IrgendeineGObjectKlasse, GObjectDeleter> object(gib_mir_objekt_mit_inkrementiertem_referenzzaehler());
    

    ... das erspart eine ganze Menge redundante g_object_unref() mit denen man sonst alle nur erdenklichen Code-Pfade zupflastern muss, wenn man nicht auf unübersichtliche goto -Anweisungen zurückgreifen will.

    großbuchstaben schrieb:

    Objekte mit genau bekannter Lebensdauer und mehreren Benutzern;

    Wie eingangs erwähnt, irgendwer ist der Besitzer über den sich die "bekannte Lebensdauer" definiert. Ich habe z.B. auch Objkete in meinen Projekten die immer einem Elternobjekt zugeordnet sind, und höchstens so lange leben wie das Elternobjekt. In diesem Fall sehe ich ein Problem darin, wenn die Kinder einen nackten Eltern-Pointer haben - schließlich ist das kein "besitzender" Zeiger.

    großbuchstaben schrieb:

    legacy code;

    Ja, da kommt man vielleicht manchmal nicht drumherum, es schadet aber nicht für neue Code-Teile trotzdem auf die Smart-Pointer zurückzugreifen und an den Schnittstellen ähnlich zu verfahren wie bei C-Bibliotheken (s.o.)

    Finnegan



  • Finnegan schrieb:

    Wichtig ist für mich: Wem gehört das Objekt? (sprich: Wer gibt es wieder frei?).

    in der von mir beschriebenen Anwendung: dem Benutzer, der über die DLL externs die Funktionen abruft, mit denen die Daten verarbeitet werden. Da nur der Benutzer (bzw sein top-level Programm) weiß, wann er alle DLL Funktionen zur Verarbeitung aufgerufen hat, kann auch nur er bestimmen, wann das Datenpaket-Objekt gelöscht werden kann.

    Ein smart pointer müßte zu diesem Zweck schon ziemlich smart sein, d.h. die Intention des Benutzers bzw top-level Programms erkennen, um zu wissen, wann das Datenpaket gelöscht werden kann, weil keine zukünftigen Funktionsabrufe mehr kommen, die sich auf das betreffende Objekt beziehen.


  • Mod

    großbuchstaben schrieb:

    in der von mir beschriebenen Anwendung: dem Benutzer, der über die DLL externs die Funktionen abruft, mit denen die Daten verarbeitet werden. Da nur der Benutzer (bzw sein top-level Programm) weiß, wann er alle DLL Funktionen zur Verarbeitung aufgerufen hat, kann auch nur er bestimmen, wann das Datenpaket-Objekt gelöscht werden kann.

    Und warum nun new und rohe Pointer? Der Benutzer ist hier doch eindeutig der Besitzer.

    Interfacing mit C;

    Erzähl mal, wieso deine Schnittstelle sich für die Interna interessiert.

    Objekte mit genau bekannter Lebensdauer und mehreren Benutzern;

    Also ein automatisches Objekt?

    legacy code;

    Aha. Es ist also ein guter Grund, new und rohe Pointer benutzen, weil andere Leute auf der Welt es falsch gemacht haben?



  • großbuchstaben schrieb:

    in der von mir beschriebenen Anwendung: dem Benutzer, der über die DLL externs die Funktionen abruft, mit denen die Daten verarbeitet werden. Da nur der Benutzer (bzw sein top-level Programm) weiß, wann er alle DLL Funktionen zur Verarbeitung aufgerufen hat, kann auch nur er bestimmen, wann das Datenpaket-Objekt gelöscht werden kann.

    Es ist ja nicht so, dass Smartpointer die Objekte völlig willkürlich löschen. Man hat ja schon die Kontrolle darüber - man muss lediglich dafür sorgen, dass das Smartpointer-Objekt so lange existiert, wie das Datenpaket benötigt wird.

    großbuchstaben schrieb:

    Ein smart pointer müßte zu diesem Zweck schon ziemlich smart sein, d.h. die Intention des Benutzers bzw top-level Programms erkennen, um zu wissen, wann das Datenpaket gelöscht werden kann, weil keine zukünftigen Funktionsabrufe mehr kommen, die sich auf das betreffende Objekt beziehen.

    Ich habe das mit den Datenpaketen zwar immer noch nicht so ganz verstanden, aber irgendwie riecht das ein wenig nach einem Fall für std::shared_ptr .
    Wenn man nicht weiss wann und für und wie lange etwas "zukünftig" gebraucht wird, bekommt halt jeder der (asynchron) etwas damit machen will einen Shared-Pointer auf das Datenpaket mit auf den Weg, und löscht diesen, wenn er damit fertig ist. Das delete passiert dann automatisch wenn alle mit dem Ding durch sind. Gerade in Multithreaded-Code ist std::shared_ptr oft ein wahrer Segen 😃

    und nebenbei: So "smart" braucht so ein Shared Pointer dabei gar nicht zu sein. Wenn es im Programmcode keine Referenz mehr auf ein Objekt gibt (und man sich nicht hinterlistigerweise einen nicht-Shared-Pointer darauf herausgewieselt hat), dann kann niemand mehr darauf zugreifen oder einen Funktionsaufruf damit machen, weil niemand mehr weiss wo sich das Objekt im Speicher befindet - ergo kann man es auch ruhigen Gewissens löschen.

    Finnegan



  • SeppJ schrieb:

    Und warum nun new und rohe Pointer? Der Benutzer ist hier doch eindeutig der Besitzer.

    der Benutzer sitzt aber nicht in der DLL, sondern ruft externs auf. Asynchron. Mannmannmannnmannmann ...

    Objekte mit genau bekannter Lebensdauer und mehreren Benutzern;

    Also ein automatisches Objekt?

    kommt drauf an. Wenn die Erzeugung von Objekten asynchron von einer darüber liegenden Schicht veranlaßt wird, eher nicht.

    Aha. Es ist also ein guter Grund, new und rohe Pointer benutzen, weil andere Leute auf der Welt es falsch gemacht haben?

    hab' keine Lust, mich zu wiederholen 🙄


  • Mod

    großbuchstaben schrieb:

    Finnegan schrieb:

    Wichtig ist für mich: Wem gehört das Objekt? (sprich: Wer gibt es wieder frei?).

    in der von mir beschriebenen Anwendung: dem Benutzer, der über die DLL externs die Funktionen abruft, mit denen die Daten verarbeitet werden. Da nur der Benutzer (bzw sein top-level Programm) weiß, wann er alle DLL Funktionen zur Verarbeitung aufgerufen hat, kann auch nur er bestimmen, wann das Datenpaket-Objekt gelöscht werden kann.

    Ein smart pointer müßte zu diesem Zweck schon ziemlich smart sein, d.h. die Intention des Benutzers bzw top-level Programms erkennen, um zu wissen, wann das Datenpaket gelöscht werden kann, weil keine zukünftigen Funktionsabrufe mehr kommen, die sich auf das betreffende Objekt beziehen.


    Falls die Zerstörung des Objektes vom Client-Programm initiiert werden muss, dann ist der Client der Besitzer, und die Frage nach dem Smartpointer stellt sich auch nur für das Clientprogramm.


  • Mod

    großbuchstaben schrieb:

    SeppJ schrieb:

    Und warum nun new und rohe Pointer? Der Benutzer ist hier doch eindeutig der Besitzer.

    der Benutzer sitzt aber nicht in der DLL, sondern ruft externs auf. Asynchron.

    Ja, und?

    Wenn die Erzeugung von Objekten asynchron von einer darüber liegenden Schicht veranlaßt wird, eher nicht.

    Ja, und? Ist "asynchron" für dich irgendwie ein Zauberwort für undefinierte Besitzverhältnisse?



  • camper schrieb:



  • Finnegan schrieb:

    Wenn man nicht weiss wann und für und wie lange etwas "zukünftig" gebraucht wird, bekommt halt jeder der (asynchron) etwas damit machen will einen Shared-Pointer auf das Datenpaket mit auf den Weg, und löscht diesen, wenn er damit fertig ist. Das delete passiert dann automatisch wenn alle mit dem Ding durch sind.

    eben nicht. Wann wer damit fertig ist, kann innerhalb der DLL nicht festgestellt werden, weil die Auswahl und Reihenfolge der Funktionen an einer höheren Schicht liegt, auf die die DLL keinen Zugriff hat.

    Also nix mit automatisch löschen und sowas. Schön konservativ mit Zeiger und new.

    Es bleibt bei dem, was ich in meinem ersten Post schrieb: raw pointers und new haben ihre Anwendungen, und damit ist die Diskussion für mich beendet.



  • großbuchstaben schrieb:

    Finnegan schrieb:

    Wenn man nicht weiss wann und für und wie lange etwas "zukünftig" gebraucht wird, bekommt halt jeder der (asynchron) etwas damit machen will einen Shared-Pointer auf das Datenpaket mit auf den Weg, und löscht diesen, wenn er damit fertig ist. Das delete passiert dann automatisch wenn alle mit dem Ding durch sind.

    eben nicht. Wann wer damit fertig ist, kann innerhalb der DLL nicht festgestellt werden, weil die Auswahl und Reihenfolge der Funktionen an einer höheren Schicht liegt, auf die die DLL keinen Zugriff hat.

    Also nix mit automatisch löschen und sowas. Schön konservativ mit Zeiger und new.

    Es bleibt bei dem, was ich in meinem ersten Post schrieb: raw pointers und new haben ihre Anwendungen, und damit ist die Diskussion für mich beendet.

    Wenn du weißt, wann es sicher ist ein C++- delete zu machen, kannst du auch genau so gut sicherstellen, dass das Smart-Pointer-Objekt bis zu eben dieser Stelle existiert, bzw. dort stattdessen ein ptr.reset() machen. Es ist ja nicht so, dass der Code mit Smart Pointern dann unbedingt "korrekter" oder "sicherer" wäre (mal abgesehen davon, dass man dann das delete auf jeden Fall nicht "vergisst" und auch die Gefahr eines use-after-free minimiert).
    Soweit ich mich erinnere, ging es doch eigentlich darum, dass man an dieser Stelle new / delete unbedingt braucht ("Anwendungen" von new und besitzenden Raw Pointern), und so wie ich das sehe ist das hier nicht der Fall - zumindest habe ich noch kein Argument gelesen, weshalb man das Problem nicht mit einem Smart Pointer lösen könnte.

    Mir persönlich sind ehrlich gesagt Pointer lieber, die versehentlich (automatsich) freigegeben werden und danach genullt sind (da weiss man wo der Feind steht :D), als solche bei denen ich eventuell das delete vergessen habe, oder schlimmer als solche die zwar gelöscht wurden, danach aber nicht genullt wurden 😉

    Und ja, ich habe auch fertig :p

    Finnegan


Anmelden zum Antworten