Mit new erstellten Zeiger in vector pushen?



  • Danke! Es sieht aber so aus:

    struct Map
    {
        // ...
    	struct Vertex
    	{
    		float x, y, z;
    	};
    
    	Vertex* vertex;
        // ...
    };
    
    // ...
    Map map;
    map.vertex = new Map::Vertex[24];
    // ...
    
    // ...
    std::vector<Map> mapList;
    // ...
    mapList.push_back(map);
    // ...
    

    Also muss ich dann

    delete [] mapList[x].vertex;

    schreiben?

    MfG



  • Mach lieber so:

    struct Map
    {
        // ...
        Map( size_t size )
        {
            vertex = new Vertex[size];
        }
        ~Map()
        {
            delete[] vertex;
        }        
        struct Vertex
        {
            float x, y, z;
        };
        Vertex* vertex;
        // ...
    };
    

    Dann kümmert sich map selbst um den Speicher und du kannst den vector ganz normal benutzen.



  • Interessant. Danke.

    MfG



  • Google mal nach RAII ( Resource Acquisition Is Initialization )



  • THX 1138 schrieb:

    Mach lieber so:

    struct Map
    {
        // ...
        Map( size_t size )
        {
            vertex = new Vertex[size];
        }
        ~Map()
        {
            delete[] vertex;
        }        
        struct Vertex
        {
            float x, y, z;
        };
        Vertex* vertex;
        // ...
    };
    

    Dann kümmert sich map selbst um den Speicher und du kannst den vector ganz normal benutzen.

    Als erster Schritt schon mal 'ne gute Sache. Sauber wird es, wenn man die sogenannte 'Regel der drei' befolgt, die da lautet: "Falls ein nicht trivialer Destruktor (oder Zuweisung-Operator oder Kopier-Konstruktor) in der Klasse auftaucht, so muss man auch die anderen beiden implementieren."

    Für den Anfang würde es auch reichen, diese einfach 'lahm zu legen' - inden man sie private macht.

    struct Map
    {
        // ...
        explicit Map( size_t size ) // explicit verhindert implizite Konvertierung
            : vertex( new Vertex[size] ) // Initialisierungslisten verwenden
        {}
        ~Map()
        {
            delete[] vertex;
        }        
        struct Vertex
        {
            float x, y, z;
        };
    private:  // 'vertex' bessser auch private machen
        Vertex* vertex;
        // ...
    private:
        Map( const Map& );
        Map& operator=( const Map& );
    };
    

    .. falls Dein Code anschließend nicht mehr compiliert, hat Dich diese Konstruktion vor einem bösen Laufzeitfehler bewart.

    Gruß
    Werner



  • "Falls ein nicht trivialer Destruktor (oder Zuweisung-Operator oder Kopier-Konstruktor) in der Klasse auftaucht, so muss man auch die anderen beiden implementieren."

    Verstehe ich nicht.

    MfG



  • Eventuell meint er Konstruktor



  • Nein, tut er glaub ich nicht 😉



  • Knuddlbaer schrieb:

    Eventuell meint er Konstruktor

    Nein, er meint Destruktor.
    Ein Destruktor gilt als nicht trivial, wenn er z.B. eine delete enthält oder auch einen anderen Aufruf, der eine Ressource gleich welcher Art wieder freigibt.

    Beim (automatischen) Kopieren eines Objekt mit Zeiger (hier vertex) wird natürlich nur der Zeiger kopiert, wird jetzt eines der beiden Objekte, also die Kopie oder das Original gelöscht, so wird sein Konstruktor aufgerufen und die Ressource freigegeben. Das andere Objekt läuft dann mit einem Zeiger herum der nicht mehr gültig ist und spätestens beim Löschen des Zweiten Objekts kracht es dann.

    Mehr dazu wenn man 'rule of three c++' bei google eingibt. Unter anderen ist es hier ganz gut beschrieben. In deutsch gibt's das auch hier

    Gruß
    Werner



  • ceplusplus schrieb:

    struct Map
    {
        // ...
    	struct Vertex
    	{
    		float x, y, z;
    	};
    
    	Vertex* vertex;
        // ...
    };
    
    // ...
    Map map;
    map.vertex = new Map::Vertex[24];
    // ...
    
    // ...
    std::vector<Map> mapList;
    // ...
    mapList.push_back(map);
    // ...
    

    Allerdings schreibe ich nach push_back():

    delete [] map.vertex;

    Trotzdem arbeite ich dann wie gewohnt mit dem Objekt im Vektor.

    MfG



  • ceplusplus schrieb:

    ceplusplus schrieb:

    std::vector<Map> mapList;
    // ...
    mapList.push_back(map);
    // ...
    

    Allerdings schreibe ich nach push_back():

    delete [] map.vertex;

    Trotzdem arbeite ich dann wie gewohnt mit dem Objekt im Vektor.

    Ja, da haben wir es ja schon 🕶

    Dringender Tipp: Füge einen Kopier- und einen Zuweisungs-Operator für Map hinzu, in Deinem Fall public und wenn Du nicht weist wie das geht, so frag' noch mal nach. Ich gehe jetzt Fernsehen schauen - aber die anderen können Dir auch helfen.

    Gruß
    Werner



  • Kopierkonstruktor ok für meinen Fall. Zuweisungsoperator ist daweil nicht dringend nötig?!

    Aber warum funktioniert es eigentlich auch ohne dem? Der Zeiger des Objekts zeigt auf ein array. Wenn man nun push_back() aufruft, wird das Objekt also kopiert... und somit auch der Zeiger. Der zeigt dann eigentlich auf die alte Adresse, die aber von delete schon wieder freigegeben wurde.

    Warum sind die Daten dann immer noch da?

    MfG



  • ceplusplus schrieb:

    Warum sind die Daten dann immer noch da?

    wahrscheinlich weil bis dahin niemand in den Speicher hineingeschrieben hat. Aber was nicht ist, kann noch kommen. Ist auch eine Frage Deiner verwendeten Umgebung. Das VisualStudio z.B. überschreibt freigegebenen Speicher im Debug-Mode und es erkennt zweimal freigegebenen Speicher als Fehler; das scheint bei Dir nicht der Fall zu sein.

    Wenn ich's so recht überlege gäbe es für Dich eine sehr einfache Lösung. Nutze einfach einen std::vector um die Vertex zu speichern.

    struct Map
    {
        struct Vertex
        {
            float x, y, z;
        };
    
        explicit Map( size_t size )
            : vertex( size ) // legt 'size' Vertex-Objekte an
        {}
    
        std::vector< Vertex > vertex;
    };
    

    und das war's schon. Destruktor, Kopie-Konstruktor und Zuweisung-Operator von Map macht alles der vector und der Compiler - und Du musst Dich um nichts mehr kümmern. Ansonsten sollte sich Dein Code dadurch nicht wesentlich verändern - wenn überhaupt, so wird er einfacher.

    Gruß
    Werner



  • Okay danke. Wenn das performant ist. Ist ein Maploader, da wird schon mal ordentlich "gepusht".

    MfG

    EDIT:
    Wozu " : vertex( size ) // legt 'size' Vertex-Objekte an " ? Verstehe ich nicht. Der std::vector wird doch die Vertexobjekte speichern.



  • Damit wird ein Vector mit der vorgegebenen Größe erstellt. Wenn du also von vorneherein weißt, wie groß dein Array wird, kannst du dir damit unnötige Kopieroperationen speichern. Denn wenn ein Vector seine Kapazitätsgrenze erreicht und noch ein Element eingefügt wird, muss der Inhalt des Vectors in ein neues Array kopiert werden.

    Dadurch erhälst du nun zwei Möglichkeiten:

    //1.
    Map map1;
    
    //2.
    Map map2(10);
    

    Damit hast du mit map1 ein Objekt, bei dem ein Vector enthalten ist, der relativ früh (wahrscheinlich nach dem 1. Einfügen) seine Größe ändern muss, d.h. auch die alten Daten kopieren. Während der Vector in map2 erst nach 10 Einfügeoperationen seine Größe ändern muss.

    Edit: Fast vergessen: für die 1. Möglichkeit, musst du aber noch den "normalen" Konstruktor explizit einfügen, weil du einen anderen bereits definiert hast. D.h.:

    Map()
    { }
    

    Der tut zwar nichts, aber sonst fällt die Erstellung von map1 beim kompilieren fehl.
    [/edit]

    Siehe:
    http://www.cppreference.com/cppvector/vector_constructors.html

    Und:

    [2] Memory will be reallocated automatically if more than capacity() - size() elements are inserted into the vector. Reallocation does not change size(), nor does it change the values of any elements of the vector. It does, however, increase capacity(), and it invalidates [5] any iterators that point into the vector.

    [3] When it is necessary to increase capacity(), vector usually increases it by a factor of two. It is crucial that the amount of growth is proportional to the current capacity(), rather than a fixed constant: in the former case inserting a series of elements into a vector is a linear time operation, and in the latter case it is quadratic.

    [4] Reserve() causes a reallocation manually. The main reason for using reserve() is efficiency: if you know the capacity to which your vector must eventually grow, then it is usually more efficient to allocate that memory all at once rather than relying on the automatic reallocation scheme. The other reason for using reserve() is so that you can control the invalidation of iterators. [5]

    Von http://www.sgi.com/tech/stl/Vector.html . Eine Beispielhafte Implementation. Die Verdoppelung der Kapazität ist aber nicht bei jeder Vectorimplementation gegeben, aber sehr verbreitet.



  • Aber ist push_back() performant genug? Eine einfache Zuweisung ist doch viel schneller, oder? Es kann hier schon mal um paar Tausend vertices gehen.

    MfG



  • Für push_back() ist im Standard festgelegt, daß es im Mittel konstante Laufzeit haben muß - besser wirst auch du das nicht hinbekommen (und solange der vector genug Kapazität übrig hat, wird sich push_back() auf eine Zuweisung und ein Inkrement beschränken).



  • Mhm...

    struct Map 
    { 
        struct Vertex 
        { 
            float x, y, z; 
        }; 
    
        explicit Map( size_t size ) 
            : vertex( size )
        {} 
    
        std::vector< Vertex > vertex; 
    };
    

    Im Elementinitialisierer ist also das vector- "vertex" Objekt gemeint?
    Würde dann

    " : vertex (2) "

    dasselbe wie

    " push_back();
    push_back(); "

    bedeuten? Also es wird nichts eingefügt, aber der Platz wird schon mal reserviert? Und bedeutet das dann, dass wenn ich vorher zB " : vertex (100) " schreibe, dass ich dann bis zu 100x push_back() schreiben kann, ohne dass das array vergrößert werden muss/wird?

    MfG



  • Im Elementinitialisierer ist also das vector- "vertex" Objekt gemeint?
    Würde dann

    " : vertex (2) "

    dasselbe wie

    " push_back();
    push_back(); "

    bedeuten? Also es wird nichts eingefügt, aber der Platz wird schon mal reserviert?

    Die Kapazität wird auf 2 gesetzt, aber es sind noch keine Objekte drin, auch keine leeren

    Und bedeutet das dann, dass wenn ich vorher zB " : vertex (100) " schreibe, dass ich dann bis zu 100x push_back() schreiben kann, ohne dass das array vergrößert werden muss/wird?

    Genau das 💡
    Und wenn du für weitere 100 Platz schaffen willst, kannst du mit vertex.reserve(100); Speicher reservieren. Dann wird zwar umkopiert, aber nur einmal für die nächsten 100.



  • So ungefähr. Ja, du ahst dann Platz für 100 Objekte, ohne dass kopiert werden muss. Das schöne daran, dass du die Größe bei der Initialisierung deiner Maps angibst, ist dass dem Vector ebenfalls die Größe zum Konstruktoraufruf bekannt ist. D.h. dieser muss auch nur einmal Speicher anfordern.

    explicit Map( size_t size )
    : vertex(size) //an dieser Stelle wird der Konstruktor von vector aufgerufen
    { }
    
    explicit Map( size_t size )
    {
       vertex.reserve(size); //hier wurde er bereits aufgerufen, d.h. es wird
                             //erneut Speicher angefordert
    }
    

Anmelden zum Antworten