Heap und Stack



  • 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