Was passiert da genau (vector)?

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Was passiert da genau (vector)?

Beitrag von starcow »

Ich stehe einmal mehr vor einem Verständnisproblem und finde darauf irgendwie keine schlüssige Antwort.

Es geht um folgendes einfaches Beispiel:

Code: Alles auswählen

#include <iostream>
#include <vector>
using namespace std;

class ding {
private:
public:
	ding() {
		cout << "Ding erstellt!" << endl;
	}
	~ding() {
		cout << "Ding geloescht!" << endl;
	}
};


void f() {
	vector<ding> haufen;
	ding objekt;
	haufen.push_back(objekt);
	haufen.push_back(objekt);
	haufen.push_back(objekt);
	return;
}

int main(int argc, char** Argv) {
	f();
	return 0;
}
Ich lege einen vector "haufen" an und befülle ihn mit Instanzen der Klasse "ding".
Soweit so einfach. Nach meiner Überlegung müssten jetzt vier Instanzen der Klasse ding erzeugt worden sein und demnach im Speicher liegen. Nämlich die lokal angelegte Instanz "objekt" und weitere drei Instanzen im vector haufen.
Und wenn nun die Funktion f() wieder verlassen wird, müssten doch auch eigentlich vier Instanzen von der Klasse "ding" gelöscht werden.

Als output bekomme ich aber folgendes angezeigt:
Ding erstellt!
Ding geloescht!
Ding geloescht!
Ding geloescht!
Ding geloescht!
Ding geloescht!
Ding geloescht!
Ding geloescht!

Und wenn ich statt drei nur zwei dinge mittels push_back() in den haufen schiebe:
Ding erstellt!
Ding geloescht!
Ding geloescht!
Ding geloescht!
Ding geloescht!

Müsste nicht schon alleine der Umstand super problematisch sein, dass nur eine Instanz erzeugt und ganze sieben (Beispiel eins) gelöscht werden?
Man stelle sich vor ich lösche über den Destruktor ein Element im Heap, dass ich zuvor über den Konstruktor erstellt hätte.

Code: Alles auswählen

Character* Kreatur; // Zeiger auf ein Character-Objekt im Heap
...
delete Kreatur;
Das müsste doch einen schweren Fehler verursachen, da ein Element versucht wird mehrere male zu löschen.
Kann mir jemand erklären, was hier passiert und ob das ganze mit rechten Dingen zu- und hergeht?

Edit:
Kann es vielleicht sein, dass anstelle des "normalen" Konstruktor eine Art unsichtbarer Kopier-Konstruktor aufgerufen wird und ich deshalb nicht geloggt bekomme, dass tatsächlich in der Summe gleich viele Ding-Instanzen erstellt, wie gelöscht werden?

Gruss starcow
Freelancer 3D- und 2D-Grafik
mischaschaub.com
NytroX
Establishment
Beiträge: 358
Registriert: 03.10.2003, 12:47

Re: Was passiert da genau (vector)?

Beitrag von NytroX »

Hi,

das liegt am CopyConstructor.
Das "ding" wird zwar nur einmal erstellt, aber mehrmals kopiert.
Füge mal den copyconstructor in deiner class hinzu, dann siehst du es:

Code: Alles auswählen

class ding {
private:
public:
	ding() {
		cout << "Ding erstellt!" << endl;
	}
	ding(const ding&) {
		cout << "Ding kopiert!" << endl;
	}
	~ding() {
		cout << "Ding geloescht!" << endl;
	}
};
Also dein Objekt wird einmal erstellt, und dann mehrmals kopiert.
Die große Frage, die sich jetzt stellt, ist natürlich:
Warum wird das Ding so oft kopiert? Also mehr als 3 mal?

Und die Antwort ist: weil der vector eine feste Größe hat.
Und wenn die Größe nicht mehr ausreicht, muss der vector neu allokiert werden, und alle Objekte darin werden dann kopiert.

ändere mal die funktion "f" in:

Code: Alles auswählen

void f() {
	vector<ding> haufen;
	haufen.reserve(10); //hier reservieren wir jetzt platz für 10 "ding"e
	ding objekt{};
	haufen.push_back(objekt);
	haufen.push_back(objekt);
	haufen.push_back(objekt);
	return;
}
Mit den beiden Änderungen von mir ist jetzt ist die Ausgabe:

Code: Alles auswählen

Ding erstellt!
Ding kopiert!
Ding kopiert!
Ding kopiert!
Ding geloescht!
Ding geloescht!
Ding geloescht!
Ding geloescht!
Es gibt außerdem noch einen Move-Contructor, dann muss das ding nicht in den vector kopiert werden (wobei das bei deinem ding aktuell egal ist, ist ja nichts drin in der Klasse).
Oder du nimmst einfach "emplace_back", dann kannst du das ding direkt im vector erstellen, ohne move oder copy.

Code: Alles auswählen

void f() {
	vector<ding> haufen;
	haufen.reserve(10);
	haufen.emplace_back();
	haufen.emplace_back();
	haufen.emplace_back();
	return;
}

Ding erstellt!
Ding erstellt!
Ding erstellt!
Ding geloescht!
Ding geloescht!
Ding geloescht!
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Was passiert da genau (vector)?

Beitrag von starcow »

Super, vielen Dank NytroX! Das leuchtet alles ein. :-)
Wie würdest du denn vorgehen, wenn du einen vector von einer separaten Funktion "befüllen" lassen möchtest?
Ihn als Referenz übergeben - und den Rückgabetyp der Funktion auf "void" setzen? Besteht da nicht das Problem, dass sich der vector aufgrund von Platzproblemen einen komplett neuen "Block" an Speicher holen könnte und somit seine Position geändert haben könnte?

Gruss starcow
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Was passiert da genau (vector)?

Beitrag von dot »

starcow hat geschrieben: 26.12.2019, 16:20Wie würdest du denn vorgehen, wenn du einen vector von einer separaten Funktion "befüllen" lassen möchtest?
Ich würde die Funktion einfach den neu befüllten vector returnen lassen… ;)

Code: Alles auswählen

vector<ding> f()
{
	vector<ding> dings;
	dings.reserve(10);
	dings.emplace_back();
	dings.emplace_back();
	dings.emplace_back();
	return dings;
}
starcow hat geschrieben: 26.12.2019, 16:20Besteht da nicht das Problem, dass sich der vector aufgrund von Platzproblemen einen komplett neuen "Block" an Speicher holen könnte und somit seine Position geändert haben könnte?
Das Problem besteht immer. Hinzufügen oder Entfernen von Elementen invalidated im Allgemeinen alle bestehenden Iteratoren/Pointer/Referenzen in den vector…
NytroX
Establishment
Beiträge: 358
Registriert: 03.10.2003, 12:47

Re: Was passiert da genau (vector)?

Beitrag von NytroX »

.. was dot sagt :-)

Der vector selbst ändert die Position im Speicher nicht, sondern nur die Daten da drin; "vector" ist auch nur eine Klasse, die intern nochmal einen pointer auf die Daten hat; den kann man sich mit .data() auch holen.
Aber es ist halt keine gute Idee, einen Pointer bzw. eine Referenz auf den Vector-Inhalt zu halten, das kann böse ins Auge gehen.
Und das schlimme ist, dass es nicht immer schief geht, sondern nur sporadisch (immer wenn die Daten die Adresse ändern).
Ich finde es einfacher, nur mit dem Index zu arbeiten.
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Was passiert da genau (vector)?

Beitrag von starcow »

Interessant. Wenn also der "vector" im wesentlichen einfach ein Zeiger ist, der auf einen Speicherblock zeigt (bei welchem man sich nicht sicher sein kann, dass er seine Adresse behält), kann ich mich dann aber im Gegenzug zu 100% darauf verlassen, dass dieser "vector-Zeiger" selbst, seine Adresse immer behält?
Denn so müsste es ja einfach und sicher sein, diesen "vector" einer Befüll-Funktion zu übergeben - indem man einfach die Adresse des vectors selbst (nicht der Elemente) übergibt.

Code: Alles auswählen

void f(vector<ding>& Dinge)
{
    // Den vector befüllen
    Dinge.push_back(...);
    return;
}

int main()
{
    vector<ding> Dinge;
    // vector befüllen lassen
    f(&Dinge);
}

@dot
Der vector der befüllt werden soll existiert eben bereits. Und wenn ich dann einen vector per "return per value" von der Funktion zurückgeben lasse, dann wird ja viel unnötige Kopierarbeit verichtet. Oder nicht?

Gruss starcow
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Antworten