vector
-
Kann bitte jemand diese unterschiedlichen Vorgänge im Zusammenhang von vector erklären:
#include <iostream> class X { public: X(){std::cout<<"ctor"<<std::endl;} ~X(){std::cout<<"dtor"<<std::endl;} X(const X& x){std::cout<<"copycon"<<std::endl;} X& operator=(const X& x){std::cout<<"op="<<std::endl; return *this;} }; /***********************************************************/ #include <vector> using namespace std; int main() { X x; cout << endl; vector<X> v(1); cout << endl; vector<X> w(3); cout << endl; w = v; cout << endl; v.push_back(x); cin.get(); }
ctor
ctor
copycon
dtorctor
copycon
copycon
copycon
dtorop=
dtor
dtorcopycon
copycon
dtor//... int main() { X x; cout << endl; vector<X> v(3); cout << endl; vector<X> w(1); cout << endl; w = v; cout << endl; v.push_back(x); cin.get(); }
ctor
ctor
copycon
copycon
copycon
dtorctor
copycon
dtorcopycon
copycon
copycon
dtorcopycon
copycon
copycon
copycon
dtor
dtor
dtor
-
push_back arbeitet mit copycon:
vector<X> v; cout << endl; X x; cout << endl; v.push_back(x);
ctor copycon
Gelöscht werden Elemente in vector, indem deren Destruktor aufgerufen wird:
vector<X> v; cout << endl; X x; cout << endl; v.push_back(x); vector<X> w; cout << endl; v=w;
ctor copycon dtor
Hier wird auch bei w=v der copycon aufgerufen:
vector<X> v; cout << endl; X x; cout << endl; v.push_back(x); vector<X> w; cout << endl; w=v;
ctor copycon copycon
So kommt auch der op= ins Spiel:
vector<X> v; cout << endl; X x; cout << endl; v.push_back(x); vector<X> w; cout << endl; w=v; v=w;
ctor copycon copycon op=
Bei einem mehrfachen push_back kommen copycon und dtor immer stärker ins Spiel:
vector<X> v; cout << endl; X x; cout << endl; v.push_back(x); cout << endl; v.push_back(x); cout << endl; v.push_back(x);
ctor copycon copycon copycon dtor copycon copycon copycon dtor dtor
Da das ständige Umkopieren (die Wohnung ist immer um eins zu eng!) ineffizient ist, erledigt man das Speicher anfordern mittels reserve():
vector<X> v; v.reserve(3); cout << endl; X x; cout << endl; v.push_back(x); cout << endl; v.push_back(x); cout << endl; v.push_back(x);
ctor copycon copycon copycon
Das vierte push_back kostet wieder drei ineffiziente copycon plus drei Mal dtor für den Umzug in eine größere Wohnung ;):
vector<X> v; v.reserve(3); cout << endl; X x; cout << endl; v.push_back(x); cout << endl; v.push_back(x); cout << endl; v.push_back(x); cout << endl; v.push_back(x);
ctor copycon copycon copycon copycon copycon copycon copycon dtor dtor dtor
So geht das verkürzt mit vier Elementen:
X x; cout << endl; vector<X> v(4,x);
ctor copycon copycon copycon copycon
-
Übrigens sieht man das alles noch besser, wenn man die Speicherstelle ausgibt:
#include <iostream> #include <vector> using namespace std; class X { public: X(){std::cout<<this<<": "<<"ctor"<<std::endl;} ~X(){std::cout<<this<<": "<<"dtor"<<std::endl;} X(const X& x){std::cout<<this<<": "<<"copycon"<<std::endl;} X& operator=(const X& x){std::cout<<this<<": "<<"op="<<std::endl; return *this;} }; int main() { vector<X> v(4); cin.get(); }
0x22ff38: ctor 0x3f2500: copycon 0x3f2501: copycon 0x3f2502: copycon 0x3f2503: copycon 0x22ff38: dtor
Hier sieht man, dass ein Objekt vom Typ X auf dem Stack erzeugt wird (ctor). Anschließend wird es vier Mal (copycon) in den freestore kopiert und das Objekt auf dem Stack wieder zerstört (dtor).
Das steht sogar falsch im Josuttis (S.149):std::vector<T> v(5) // calls five times the default constructor of type T
-
Nun zur Ausgangsfrage. Wir verwenden eine Klasse X, die uns beim copycon und op= auch die Herkunft verrät:
#include <iostream> #include <vector> using namespace std; class X { public: X(){std::cout<<this<<": "<<"ctor"<<std::endl;} ~X(){std::cout<<this<<": "<<"dtor"<<std::endl;} X(const X& x){std::cout << this << ": " << "copycon von " << &x << std::endl;} X& operator=(const X& x){std::cout << this << ": " << "op= von " << &x << std::endl; return *this;} }; int main() { X x; cout << endl; vector<X> v(1); cout << endl; vector<X> w(3); cout << endl; w = v; cout << endl; v.push_back(x); cin.get(); }
0x22ff58: ctor 0x22ff28: ctor 0x3f2528: copycon von 0x22ff28 0x22ff28: dtor 0x22ff28: ctor 0x3f2530: copycon von 0x22ff28 0x3f2531: copycon von 0x22ff28 0x3f2532: copycon von 0x22ff28 0x22ff28: dtor 0x3f2530: op= von 0x3f2528 0x3f2531: dtor 0x3f2532: dtor 0x3f2538: copycon von 0x3f2528 0x3f2539: copycon von 0x22ff58 0x3f2528: dtor
Zweiter Fall:
X x; cout << endl; vector<X> v(3); cout << endl; vector<X> w(1); cout << endl; w = v; cout << endl; v.push_back(x);
0x22ff58: ctor 0x22ff28: ctor 0x3f2528: copycon von 0x22ff28 0x3f2529: copycon von 0x22ff28 0x3f252a: copycon von 0x22ff28 0x22ff28: dtor 0x22ff28: ctor 0x3f2530: copycon von 0x22ff28 0x22ff28: dtor 0x3f2538: copycon von 0x3f2528 0x3f2539: copycon von 0x3f2529 0x3f253a: copycon von 0x3f252a 0x3f2530: dtor 0x3f2530: copycon von 0x3f2528 0x3f2531: copycon von 0x3f2529 0x3f2532: copycon von 0x3f252a 0x3f2533: copycon von 0x22ff58 0x3f2528: dtor 0x3f2529: dtor 0x3f252a: dtor
Jetzt liegt alles offen auf dem Tisch.
Noch ein anderes Beispiel:
vector<X> v(3); cout << endl; list<X>l(3); cout << endl; v.assign(l.begin(),l.end());
0x22ff38: ctor 0x3f2500: copycon von 0x22ff38 0x3f2501: copycon von 0x22ff38 0x3f2502: copycon von 0x22ff38 0x22ff38: dtor 0x22ff38: ctor 0x3f25b8: copycon von 0x22ff38 0x3f25c8: copycon von 0x22ff38 0x3f25d8: copycon von 0x22ff38 0x22ff38: dtor 0x3f2500: op= von 0x3f25b8 0x3f2501: op= von 0x3f25c8 0x3f2502: op= von 0x3f25d8