Objeke referenzen gebrauch, wie object copy vermeiden?
-
Hallo,
ich bin verwirrt was Anlegen und Kopieren von Objekten betrifft:
- Ich lege eine Anzahl objekte an (verschachtelt)
- ich lass die objekte ausgeben
#include <iostream> #include <vector> using namespace std; class A { public: A(std::string _n) : n(_n) { cout << "create: " << n << endl; } ~A() { cout << "delete: " << n << endl; } std::string getN() { return n; } private: std::string n; }; class O { public: O(std::string _n, A & _a) : n(_n), a(_a) { cout << "create :" << n << endl; } string getN() { return n; } private: std::string n; A a; }; class E { public: E(O & _o) : o(_o) { cout << "create e" << endl; } ~E() { cout << "delete e" << endl; } O getO() { return o; } private: O o; }; class S { public: void add_e(O & o) { E * e = new E(o); e_v.push_back(e); } void list_as() { for (unsigned int i=0; i < e_v.size(); i++) { E * new_e = e_v[i]; cout << new_e->getO().getN() << endl; } } private: vector<E *> e_v; }; int main() { cout << "hello" << endl; A a0("abc"); A a1("def"); A a2("hij"); O o0("123", a0); O o1("456", a0); E e0(o0); S s; s.add_e(o0); s.add_e(o1); s.list_as(); }
warum gibt es bei der ausgabe "create" und "delete" ausgaben? wie vermeidet man die?
hello create: abc create: def create: hij create :123 create :456 create e create e create e 123 delete: abc <<<<<<<<<<<<<< 456 delete: abc <<<<<<<<<<<<<<< delete e delete: abc delete: abc delete: abc delete: hij delete: def delete: abc
-
warum gibt es bei der ausgabe "create" und "delete" ausgaben?
Warum nicht?
wie vermeidet man die?
Man gibt nichts aus?
-
Du definierst ein paar Variablen von Klassentypen in der
main
, deren Konstruktoren und Destruktoren sollten doch nicht weiter überraschend sein. Gibt es irgendeine dieser vielen Ausgaben, die du wirklich nicht verstehst?
-
@Bashar danke, dass objekte beim anlegen (schritt 1) und beim programmende die constructors/destructors aufrufen verstehe ich
meine frage wäre gibt es bei der ausgabes.list_as();
einen weg keine temp variablen zu gebrauchen, wo die constructors/destructors geskipped werden? die objekte existieren ja schon - wieso gibt es nochmal create/delete ausgaben?
-
In
S::list_as
, im Ausdrucknew_e->getO().getN()
rufst duE::getO
auf:O getO() { return o; }
Das erfordert eine Kopie von
o
. Da du keinen Kopierkonstruktor definiert hast, gibt es dafür auch keine Ausgabe, erst wenn die Kopie wieder zerstört wird im Destruktor. Wenn du keine Kopie willst, kannst du auch eine konstante Referenz aufo
zurückgeben:const O& getO() { return o; }
-
Ein häufiger Fehler, den man am Anfang macht.
Wenn du Textausgaben machen willst um zu prüfen, was wie wann wo erzeugt und zerstört wird, was ich vorbildlich finde, dann musst du aber auch alle Konstruktoren mit "Textausgaben" versehen.Es gibt noch den "Copy-Constructor"
A( const A & ) { }
und den Move-Constructor
A( A && ) { }
Wenn du verhindern willst, dass kopiert wird, kannst du auch all diese Operatoren und Konstruktoren entfernen.
A( const A & ) = delete; A( A && ) = delete; A &operator=( const A & ) = delete; A &operator=( A && ) = delete;
Du wirst aber feststellen, dass dein Quellcode dann nicht mehr kompiliert
Das "deleten" ist eine gute Methode, wenn man sicher sein will, dass Objekte nicht kopiert werden.
-
@Bashar , @It0101 sagte in Objeke referenzen gebrauch, wie object copy vermeiden?:
Vielen Dank - euer feedback hilf meiner Lernkurve um einiges. Ich versuche nun:A( const A & )
{
cout << "copy a" << endl;
}Damit sehe ich dass die 50% meiner Objekte hierüber angelegt werden. In der Ausgabe bekomme ich immer noch "dekete" und "copy" ausgaben:
hello part 1: setup objects create: a0 create: a1 copy a create: o1 copy a create: o2 copy a create: o3 copy a create e o1 copy a create e o2 ---- part 2: list objects copy a o1 delete: copy a o2 delete: delete: delete: delete: delete: a1 delete: a0
Da die Ojbekte ja im Speicher sind (durch Schritt 1: Anlegen der Objekte), müssten wir doch im Schritt 2 copy/delete von temp memory vermeiden können?
Unten das aktuelle Programm:
#include <iostream> #include <vector> #include <string> using namespace std; class A { public: A(std::string _n) : n(_n) { cout << "create: " << n << endl; } A( const A & ) { cout << "copy a" << endl; } /* A(A & _a) { this->n = _a.n; } */ ~A() { cout << "delete: " << n << endl; } const std::string & getN() { return n; } private: std::string n; }; class O { public: O(std::string _n, A & _a) : n(_n), a(_a) { cout << "create: " << n << endl; } /* O(O & _o) { this->n = _o.n; this->a = _o.a; cout << "clone : o" << endl; } */ const string & getN() { return n; } private: std::string n; A a; }; class E { public: E(O & _o) : o(_o) { cout << "create e " << o.getN() << endl; } /* E(E & _e) { this->o = _e.o; cout << "Clone e" << endl; } */ ~E() { cout << "delete e" << endl; } const O & getO() { return o; } private: O o; }; class S { public: void add_e( O & o) { E * e = new E(o); e_v.push_back(e); } void list_as() { for (unsigned int i=0; i < e_v.size(); i++) { // E * new_e = e_v[i]; O o = e_v[i]->getO(); cout << o.getN() << endl; } } private: vector<E *> e_v; }; int main() { cout << "hello" << endl; cout << "part 1: setup objects" << endl; A a0("a0"); A a1("a1"); O o0("o1", a0); O o1("o2", a0); O o2("o3", a1); S s; s.add_e(o0); s.add_e(o1); cout << "---- part 2: list objects" << endl; s.list_as(); }
-
ok, verstehe, ich kann auch ein alias (const referenz) benutzen:
private: std::string n; const A & a;
damit fällt das kopieren weg
-
@patrickm123 sagte in Objeke referenzen gebrauch, wie object copy vermeiden?:
O o = e_v[i]->getO();
Auch wenn getO eine const ref zurückgibt, kopierst du hier in die Variable o. Du müsstest das const& mitschleppen, also
const O& o = e_v[i]->getO();
.Und was auch ein Problem bei deinem Code ist: du hast ein
vector<E*>
und erzeugst deine Kopien von Hand mitnew
. Dann musst du auch sicherstellen, dass du deine Objekte auch wieder mitdelete
löscht! (Oder einfach einenvector<E>
nehmen oderstd::vector<std::unique_ptr<E>>
(odervector<shared_ptr<E>>
) benutzen)
-
Um mal noch zu ergänzen was WOB gesagt hat:
Deine Klasse "S" hat keinen (selbst definierten) Konstruktor und Destruktor. Der Compiler baut dir hier automatisch jeweils einen von dieser Sorte. Der generierte Destruktor löscht zwar deinen Vector, aber da du im Vector Pointer hast, räumt der Vector quasi die Pointer weg ( 64bit ) aber nicht das Objekt auf das diese Pointer zeigen ( Größe: sizeof ( E ) ), denn dass musst du von Hand mit "delete" machen. Und daher musst du selbst einen Destruktor definieren.
Anders wäre die Lage wenn du in dem Vector nicht E* sondern E speichern würdest, also die Objekt direkt im Vector. Dann würde der auto-generierte Destruktor ausreichen um sauber aufzuräumen.
-
@patrickm123 sagte in Objeke referenzen gebrauch, wie object copy vermeiden?:
ok, verstehe, ich kann auch ein alias (const referenz) benutzen:
private: std::string n; const A & a;
damit fällt das kopieren weg
Sind dir die Konsequenzen dieser Änderung bewusst?
-
@hustbaer guter punkt, d.h. änderungen in "a" sind nicht möglich über ein o Objekt. Allerdings wenn A jetzt viel Speicher brauchen würde, würde das Kopieren wegfallen?
-
@patrickm123 das andere, wichtigere Problem ist aber auch, dass nun das o-Objekt komplett von dem a-Objekt, mit dem es erzeugt wurde, abhängt. Das heißt, wenn jemand das a ändert, ist es in deinem o auch geändert. Wenn jemand das a-Objekt löscht, ist deine o-Objekt kaputt (die Referenz ist ungültig).
-
@It0101 danke für den Hinweis mit dem Aufräumen - die pointer variante hatte ich deshalb genommen, weil ich dynamisch Objekte in S anlegen wollte, ohne Objekte zu kopieren, es ist eher eine Lernübung. In einem "richtigen" Programm würde ich wohl Memory profiling machen müssen?
-
@wob danke, interessanter Punkt, dass mit dem Löschen wäre nicht so gut.... d.h. müsste man dann schauen wie man es z.B. über tests garantieren kann, dass das Programm ein gültiges a enthält
-
nein man müsste sein programmdesign so aufstellen, dass so etwas nicht vorkommen kann..........
-
im destructor, sollte ich auf "pointer is not null" checken bevor ich delete aufrufe?
-
Nein, solltest du nicht.
delete
funktioniert auch auf Nullpointern.
-
@patrickm123 sagte in Objeke referenzen gebrauch, wie object copy vermeiden?:
im destructor, sollte ich auf "pointer is not null" checken bevor ich delete aufrufe?
Es darf nur nicht uninitialisiert sein bzw nicht "schon mal vorher deleted".
-
@patrickm123 sagte in Objeke referenzen gebrauch, wie object copy vermeiden?:
@It0101 danke für den Hinweis mit dem Aufräumen - die pointer variante hatte ich deshalb genommen, weil ich dynamisch Objekte in S anlegen wollte, ohne Objekte zu kopieren, es ist eher eine Lernübung. In einem "richtigen" Programm würde ich wohl Memory profiling machen müssen?
Du solltest nicht panische Angst vor dem Kopieren haben. Du musst dir bei deinem Design vor allem darüber klar sein, wem was gehört.
Deswegen wäre es z.B. unüblich, ein Objekt auf dem Heap ( z.B. mit new erzeugt ), dass Klasse A gehört, einer anderen Klasse B als rohen Pointer zu übergeben, die mit Klasse A rein gar nichts zu tun hat. In dem Fall wäre eine Kopie durchaus in Ordnung. Oder ein alternatives Konzept wie "shared_ptr". Aber der Fokus sollte immer auf den Besitzverhältnissen liegen. Der Besitzer einer Ressource erzeugt sie und zerstört sie im Idealfall auch wieder. Und wenn die Ressource weitergeben wird ( aus A entfernt und nach B übergeben ), dann gibt es auch dafür wieder bessere Varianten als reine Pointer. Z.B. std::unique_ptr und/oder move-Semantik.Aus meiner Sicht ist die Nutzung von reinen Pointer eine absolute Notlösung wenn alle anderen Konzepte nicht tragbar sind.