Richtige verwendung von new in C++



  • Hmm. Ich denke man kann die Frage in der Praxis nicht so allgemein beantworten. Die einen machen es so, die anderen so. Man kann den Speicher "von Hand" anfordern, freigeben oder man lässt z.B das löschen einem Smart Pointer.

    Wenn man einen grösseren Speicherblock lediglich für eine Funktion, respektive Berechnung braucht, dann wird man da den Speicher holen und dann am Ende einfach wieder freigeben. Man kann das von Hand tun, oder wie gesagt einen Smart Pointer tun lassen, was halt gerade die Anforderungen besser erfüllt.

    Mit langlebig ist gemeint, dass man im Vornherein nicht weiss, wie lange ein Objekt lebt, respektive in welchem Scope es sich befindet. (Bei einer Berechnung wäre das ja meist recht klar, aber da es halt ein grosses Obejekt ist, holt man es halt dynamisch, um den Stack nicht zu überstrapazieren).



  • Ich gebe Dir mal ein Beispiel aus meinem aktuellen Projekt. Es handelt sich dabei um Framework für ein Spiel.
    Ich habe eine Klasse ResourceManager. Diese lädt alle Resourcen in Gruppen entweder aus XML oder über Klassenaufrufe. Wenn ich jetzt eine Gruppe anlege, dann könnte ich einfach ein Objekt mit objekt a erstellen und dieses in meine ResourcenListe kopieren. Das geht aber nicht so einfach, da im Destruktor dieser Objekte ( es handelt sich um selbst angelegte Container für Objekte aus OpenGL ) die Resourcen wieder zerstört werden. Also muss ich verhindern dass dieses Objekt zerstört wird wenn ich meine Lade-Funktion verlasse. Das mache ich mit new. Denn im Gegensatz zu "normal" angelegten Objekten lebt dieses Objekt weiter bis ich es mit delete zerstöre. Das passiert dann beim entladen einer Resourcengruppe oder beim zerstören des Resourcenmanagers was wiederrum von einem Controller gesteuert wird, damit die Objekte freigegeben werden bevor OpenGL geschlossen wurde.
    Ich hoffe das war verständlich und ich möchte dir als Beispiel noch einen Codeauszug präsentieren:

    // Texturlader
    NLITexture* NLTextureLoader::getTexture(const char* name, const u8* data, u32 size)
    {
        if ( !glIsEnabled(GL_TEXTURE_2D) ) {
            glEnable(GL_TEXTURE_2D);
        }
        corona::File* file = NULL;
        corona::Image* img = NULL;
    
        // Load the image from memory
        file = corona::CreateMemoryFile(data, size);
        img = corona::OpenImage(file, corona::PF_DONTCARE, corona::FF_AUTODETECT);
        if (!img)
        {
            NLError(std::string("Cannot load Image '") + name + std::string("'."));
            return NULL;
        }
        u32 id = this->makeGLTexture(img->getPixels(), img->getWidth(), img->getHeight(), getColorsFromFormat(img->getFormat()));
        delete img;
    
        // Hier wird das handle angelegt und mit new alloziert. Nach der Rückgabe
        // des Texturhandles ist dieses weiterhin vorhanden und wird im Resourcen-
        // Manager zerstört bei Bedarf.
        NLITexture *handle = new NLTexture(name, id);        
        return handle;
    }
    


  • new wird für eine dynamische statt einer statischen Speicherbelegung, z.B. für einen grossen Array, eingesetzt. Lohnen tut sich dann, wenn man erst während der Laufzeit des Programmes weiss, wieviel man konkret braucht. Sobald man den angeforderten Speicher nicht mehr braucht, gibt man ihn mit delete wieder frei. Sinnvoll ist das aber nur, wenn man es mit einem wirklich grösseren Speicherbedarf zu tun haben kann.



  • Stell dir doch mal vor, du hast ein Spiel.

    Jedes Objekt im Spiel ist auch in deinem Code ein Objekt.

    Jetzt taucht ein neues Objekt auf.
    Wie realisierst du es?

    Genau, du erstellst mit new ein neues Objekt und speicherst die Addresse in deiner Objektliste.



  • Icematix schrieb:

    Stell dir doch mal vor, du hast ein Spiel.

    Jedes Objekt im Spiel ist auch in deinem Code ein Objekt.

    Jetzt taucht ein neues Objekt auf.
    Wie realisierst du es?

    Genau, du erstellst mit new ein neues Objekt und speicherst die Addresse in deiner Objektliste.

    Das ist sicher richtig. Doch das versteht der Fragesteller nicht. Würde er es verstehen, hätte er mit new / delete keine Probleme und hätte nie gefragt!



  • Hallo,

    danke für eure Beiträge. Endlich ist dank eurer Hilfe etwas Licht im Wald zu sehen.

    Eines hätte ich da noch anzumerken. Die Funktion von Scorcher24 gibt ein Image zurück, das im Heap liegt.

    img = corona::OpenImage(file, corona::PF_DONTCARE, corona::FF_AUTODETECT);
    //..
    delete img;
    

    Woher weiß man, ob das Objekt im Heap liegt oder im Stack. Das Objekt könnte doch auch von der Klasse verwaltet werden.

    Gut bei Image ist es wohl klar, aber bei anderen Funktionen vielleicht nicht zu 100%. Sollten die Funktionen mit gewisse Schlüsselwörter wie "open", "load", ... beginnen. Oder muss in der Doku der Return Type + Speicherplatz explizit angegeben werden. Letzteres führt meiner Meinung nach sehr leicht zu Problemen.

    Etwas Offtopic, aber trotzdem von sehr hohem Interesse. Bei pthread ist es möglich, bei pthread_create der auszuführenden Funktion Parameter zu übergeben. Für mehrere Parameter benutzt man eine struct. So weit, so gut. Ich frage mich, kann man das vielleicht auch etwas anders umsetzen. Und zwar ohne struct, damit folgendes möglich wird.

    class A {
    
      string val;
    
    public:
      A(string s) : val(s) { };
    }
    
    class B {
    
      string val, val2;
    
    public:
      B(string s, string ss) : val(s), val2(ss) { };
    }
    
    class C {
    
      string val;
      int valInt
    
    public:
      A(int i, string s) : val(s) valInt(i) { };
    }
    
    template<class T>
    T* createObject( ... )
    {
      return new T(/** Was auch immer, nichts funktioniert **/);
    }
    
    int main(int argc, const char* argv[]) {
      A* aObj = createObject<A>("abc");
      B* bObj = createObject<B>("abc", "ssDef");
      C* cObj = createObject<C>(154, "abc");
    }
    

    Das größte Problem ist die variable Anzahl und Typen. C++ bietet hierzu zu wenige Möglichkeiten. Hilft hier vielleicht Boost?

    Warum so etwas? Sagen wir mal, darüber zerbreche ich mir schon seit Jahren den Kopf und komme nicht drauf. Eine große Last würde abfallen 🙂

    Gruß,
    Thomas



  • Eines hätte ich da noch anzumerken. Die Funktion von Scorcher24 gibt ein Image zurück, das im Heap liegt.

    img = corona::OpenImage(file, corona::PF_DONTCARE, corona::FF_AUTODETECT);
    //..
    delete img;
    

    Woher weiß man, ob das Objekt im Heap liegt oder im Stack. Das Objekt könnte doch auch von der Klasse verwaltet werden.

    Das erfährt man bei der Nutzung der Dokumentation von corona, einer Imagelibrary. Diese gibt einen Pointer zurück, den man selbst wieder zerstören muss. Nicht gerade aktueller Standard, ich weiss, aber die Lib ist ein wenig betagt.
    Wichtig war mir eigentlich der Code am Ende, an dem ich das Textur Objekt erzeuge und zurückgeben zur weiteren Verwaltung.
    Aber bei Libraries erfährt man sowas aus der Doku bzw sollte man. Wenn nicht, ist die Doku schlecht :).

    /edit
    Ein wichtiger Grundsatz noch:
    Ein new erfordert delete.
    Ein new [] erfordert delete [].
    🙂

    rya.



  • Wenn nicht, ist die Doku schlecht :).

    Die gibt es wohl wie Sand am Meer.

    /edit
    Ein wichtiger Grundsatz noch:
    Ein new erfordert delete.
    Ein new [] erfordert delete [].
    🙂

    Klar, spielst du auf den zweiten Beitrag an? Gut, aber darum geht es mir nicht. Gibt es ein Möglichkeit, das zu lösen?



  • Hallo Siassei,

    bzgl. deiner Offtopic-Frage:

    Im aktuellen C++ Standard gibt es nur die Möglichkeit, dafür verschiedene (überladene) Template-Funktionen anzubieten, d.h.

    template<class T>
    T* createObject()
    {
      return new T();
    } 
    
    template<class T, typename P1>
    T* createObject(const P1 &p1)
    {
      return new T(p1);
    } 
    
    template<class T, typename P1, typename P2>
    T* createObject(const P1 &p1, const P2 &p2)
    {
      return new T(p1, p2);
    } 
    
    ...
    
    // mittels BOOST_PP kamm man zwar die Schreibarbeit etwas verkürzen, aber dann wird es m.E. noch unübersichtlicher
    

    Im nächsten C++ Standard C++0x wird es dann die Vereinfachung über 'variadic templates' geben.



  • Im nächsten C++ Standard C++0x wird es dann die Vereinfachung über 'variadic templates' geben.

    Stimmt, da war doch was. Geil, und gcc 4.4 unterstützt das bereits 🙂

    Danke, Thomas



  • Falls Du noch nicht rausgefunden hast, wie so etwas in C++0x aussehen würde:

    #include <iostream>
    #include <utility> // std::forward
    #include <memory>  // std::unique_ptr
    
    template<class T, class... Args>
    inline std::unique_ptr<T> make_unique(Args&&...args)
    {
       return { new T(std::forward<Args>(args)...) };
    }
    
    int main()
    {
      auto upi = make_unique<int>(42);
      std::cout << *upi << '\n';
    }
    

    ...sollte gehen. Lass mich mal zählen, wieviele C++0x Neuigkeiten bei dem Beispiel enthalten sind ... Variadische Templates, std::unique_ptr, Rvalue-Referenzen, die neue Initialisierungs-Syntax und Typinferenz (auto), also 5.

    😃

    Gruß,
    SP



  • Danke Sebastian für deinen Beitrag, ehrlich gesagt hatte ich bis jetzt noch keine Zeit, das Studium fordert zur Zeit seinen Tribut 🙂

    Mal sehen, std::forward leitet die rValue weiter und es wird der Move-Konstruktor von T aufgerufen. Was ist, wenn es keinen Move-Operator gibt, wird dann der Copy-Konstruktor aufgerufen?
    unique_ptr dient zum Verwalten des rohen Zeigers. Mhh, diesen kann ich nicht per rValue zurück geben, da der unique_ptr beim verlassen der Methode nicht mehr existiert.

    Habe ich da was falsch verstanden? Ich frage nach, da das ein Hobby von mir ist und ich die neuen Funktionen noch nicht zu 100% begreife.

    Gruß,
    Thomas



  • Siassei schrieb:

    Mal sehen, std::forward leitet die rValue weiter und es wird der Move-Konstruktor von T aufgerufen.

    std::forward = generell weiterleiten, ob lvalue oder rvalue ist egal. Es ist notwendig, da benannte Rvalue-Referenzen nach der Initialisierung wie Lvalues behandelt werden. Diese Details sind aber wohl off-topic. Ich konnte es nur nicht lassen, dieses kleine C++0x Beispiel abzuschicken. 🙂

    Siassei schrieb:

    Was ist, wenn es keinen Move-Operator gibt, wird dann der Copy-Konstruktor aufgerufen?

    Genau.

    Siassei schrieb:

    unique_ptr dient zum Verwalten des rohen Zeigers. Mhh, diesen kann ich nicht per rValue zurück geben, da der unique_ptr beim verlassen der Methode nicht mehr existiert.

    Doch, und genau das passiert ja auch im dem Beispiel. Du kannst einen unique_ptr nicht kopieren, aber dafür "umziehen lassen". Hier wird nur ein int erzeugt und am ende von main automatisch (im Destruktor von upi ) wieder freigegeben.

    Gruß,
    SP


Anmelden zum Antworten