Pointer
-
Klaus82, das "unique" in
unique_ptr
biezieht sich wie das "shared" inshared_ptr
auf die Besitzverhältnisse. Einunique_ptr
fühlt sich alleinig verantwortlich für die Löschung des referenzierten Objekts (unique ownership). Einshared_ptr
teilt sich diesen Besitz (shared ownership) mit anderenshared_ptr
Instanzen. Allerdings müssen die sich irgendwie alle mal "getroffen" haben und voneinander wissen. Damit meine ich folgendes:Falsch:
int* p = new int(23); shared_ptr<int> sp1(p); shared_ptr<int> sp2(p);
Warum? Weil sowohl sp1 und sp2 nichts voneinander wissen und jeder sich als alleiniger Besitzer sieht, der das int-Objekt löschen wird. Beide haben einen Referenzzähler erzeugt. Es gibt hier also zwei Referenzzähler. Das führt dann irgendwann zu einer Doppel-Löschung, einmal wenn der erste Referenzzähler 0 wird und dann nochmal wenn der zweite Referenzzähler 0 wird. Ganz doofe Sache also. Der Fehler ist die letzte Zeile. Der hier verwendete Konstruktor geht davon aus, dass wir ihm eine Adresse auf ein besitzerloses Objekt geben bzw dass wir den Besitz an sp2 übergeben. Das stimmt aber nicht, weil das Objekt schon im Besitz von sp1 ist.
Richtig:
int* p = new int(23); shared_ptr<int> sp1(p); shared_ptr<int> sp2 = sp1;
Hier ist alles i.O., weil der Kopierkonstruktor dafür sorgt, dass sowohl sp1 und sp2 denselben Referenzzähler verwenden. Der letzte macht das Licht aus.
Noch besser:
shared_ptr<int> sp1 = make_shared<int>(23); shared_ptr<int> sp2 = sp1;
make_shared
sorgt dafür, dass Referenzzähler, Deleter und Objekt in einem Speicherblock sitzen. Das spart eine Allokation und erhöht die Lokalität.Natürlich kannst du dir per
get
noch einen rohen Zeiger von diesen schlauen Zeigern holen.Wo ist jetzt Dein Verständnisproblem?
-
(habe mir erlaubt, deinen Code zu vereinfachen)
Klaus82 (von kk vereinfacht) schrieb:
std::unique_ptr<int> uptr(new int(10)); std::cout << uptr.get() << std::endl; std::cout << *uptr << "\n" << std::endl; int *ptr2 = new int(*uptr); std::cout << "\n" << ptr2 << std::endl; std::cout << "\n" << *ptr2 << std::endl; ptr2 = uptr.get(); std::cout << ptr2 << std::endl; return 0; }
Ich sehe hier nichts Besonderes außer einem Speicherleck. Du forderst per
new
in Zeile 6 Speicher an, überträgst die Verantwortung zur Löschung aber keinem SmartPointer und löscht das Ding auch manuell nicht perdelete
. In Zeile 11 überschreibst du die Adresse des von dir pernew
angelegten int-Objekts mit der Adresse des Objekts, auf das auch uptr zeigt. Du kannst das Objekt also nicht mehr perdelete
freigeben, weil du die Adresse nicht mehr hast.Wie gesagt "unique" bezieht sich auf den "Besitz". Und "Besitz" schließt die Verantwortung zur Löschung ein. uptr "fühlt" sich als alleiniger Besitzer des in Zeile 1 per
new
angelegten Objekts und wird es, wenn du zwischendurch keinrelease()
aufrufst, irgendwann perdelete
selbst löschen, entweder im Destruktor, im Zuweisungsoperator oder perreset
.
-
Puh,
kam wieder einiges zusammen.Ich gehe zunächst auf das Kopieren ein, weil mich das einfach am meisten wurmt: Ich kann mir nur begrenzt vorstellen wie das funktionieren soll.
Nachdem der dynamisch angeforderte Speicher und die Pointer darauf das Problem einer shallow copy sind, versuche ich mein Verständnis vom Kopieren zu beschreiben um vielleicht den Punkt zu finden wo ich falsch liege.
Ich habe z.B. zwei Pointer und einen bereits initialisiert
int *ptr1 = new int(5); int *ptr2 = new int;
Pointer 1 fordert Speicher an von der Größe eines Integers und füllt den Inhalt gemäß der Zahl 5 (ich stelle mir das salopp so vor, dass die ganzen Nullen und Einsen entsprechend gesetzt werden).
Jetzt will ich genau diese Information - die Zahl 5 - in den zweiten angeforderten Speicher kopieren. Dazu erscheint mir jetztmemcpy
sehr einleuchtend.memcpy(ptr2,ptr1,sizeof(int));
Warum? Ich stelle mir das so vor, dass der Computer zu der Adresse von Pointer 1 geht und durch
sizeof
genau weiß wie viele Adressen er weitergehen muss, um sämtliche Information die Pointer 1 verwaltet zu erfassen. Diese Information kopiert er in den Speicher auf den Pointer 2 zeigt - sprich der Computer setzt die Reihenfolge der Nullen und Einsen genauso.Für mich die Vorstellung: Wenn ich etwas kopieren möchte (egal ob Stack oder Heap?) benötige ich zunächst eine Adresse und die Information wie weit ich von da ab gehen muss. Das kann ich mir anhand diesen einfaches Beispiels eines Integers sehr gut vorstellen.
Und jetzt kommt für mich der Knackpunkt: Wenn Objekte der GSL (z.B. ein
acc
oderspline
) woher kriege ich dann die Information der Größe?Also wie funktioniert dann der Kopiervorgang memcpy(spline2,spline,sizeof(?)).
Das will mir einfach nicht in den Schädel.
Gruß,
-- Klaus.
-
Klaus82 schrieb:
...
Ja, genauso funktioniert das Kopieren via memcopy, es schiebt alle Bits von src nach dst.
Mit ints klappt das super, mit doubles auch mit std::vector und std::unique_ptr eher nicht so gut.
Sagt dir der Begriff Kopierkonstruktor etwas?
Der wird da nämlich nicht aufgerufen.
Für Typen, die im Copctor etwas anderes machen, als der Compiler im automatisch genereriertem tun würde, ist das ein Problem. Ein gewaltiges.
Deshalb: verwende nicht memcopy! Verwende nicht memcopy! Nocheinmal: Verwende nicht memcopy!
Zum Kopieren nutzt du operator=a = b;
Zum Kopieren von Arrays/Sequenzen nutzt du:
std::copy(srcbegin, srcend, dstbegin);
srcbegin ist ein Pointer/Iterator auf das erste Element, srcend ein Pointer/Iterator auf eins hinter dem letzten Element von source.
dstbegin ist ein Pointer/Iterator auf den Begin des Ziels (aufpassen, dass das groß genug ist).
Und schon brauchst du dir keine Sorgen zu machen, für ints und co läuft copy auf memcopy hinaus.
-
Klaus82 schrieb:
Wenn Objekte der GSL (z.B. ein
acc
oderspline
) woher kriege ich dann die Information der Größe?Also wie funktioniert dann der Kopiervorgang memcpy(spline2,spline,sizeof(?)).
memcpy(spline2,spline,sizeof(*spline)
Aber: Du kopierst einfach keine Strukturen/Klassen mit
memcpy()
, es sei denn Du weisst, dass eine shallow copy ausreicht.
Wie Nathan schon sagt, gibt es in C++ Kopierkonstruktor und Zuweisungsoperator.Wenn ich bei der gsl bleibe: Hier ist die Definition von
gsl_spline
:typedef struct { gsl_interp * interp; double * x; double * y; size_t size; } gsl_spline;
Wenn Du so ein Ding mit
memcpy()
(shallow) kopierst hast Du zwei splines, die sich eingsl_interp
, ein array x und ein array y teilen.
Das ist keine Kopie - das ist eher ein siamesischer Zwilling.
-
Ich weiß langsam nicht mehr so recht, wie ich es weiter beschreiben soll.
Vielleicht mal umgekehrt: Ich habe eine Klasse, die einen Pointer auf einen spline enthält
class someClass { someClass(const someClass&); gsl_spline *spline; }; // Copy Constructor someClass::someClass(const someClass& rhs):spline(rhs.spline){}
Diesen Copy Constructor müsste ich gar nicht so schreiben, weil er genau die shallow copy macht, die mir der Compiler generierte Copy Construtor auch liefern würde.
Aber anhand des Beispiels von
gsl_spline
will ich doch folgendes:
Lieber Copy Constructor,
fordere den Speicher an, denngsl_spline
benöigt. Jetzt fange bei der Adresse vonrhs.spline
an und gehe so lange weiter bis du das Ende des Speicherblocks erreicht hast. Kopiere den Inhalt diesen abgelaufenen Speicherblocks in den eben angeforderten Speicher.
Danke,
dein Klaus.Und genau zu diesem Prozess kann ich mir den
C++
Syntax nicht vorstellen. Ich könnte es mir mittelsmemcpy
odercopy
vorstellen, wenn ich die Größe des Speicherblocks ermitteln könnte.Ansonsten ...
someClass::someClass(const someClass& rhs) { gsl_spline *spline; // jetzt kopiere den Inhalt des Speicherblocks auf den rhs.spline zeigt in den eben angeforderten Speicherblock auf den spline zeigt. }
... wie ist der Syntax für den Kommentar? Vielleicht sehe ich mittlerweile einfach den Wald vor lauter Bäumen nicht mehr.
Gruß,
-- Klaus.
-
Klaus82 schrieb:
class someClass{ someClass(const someClass& rhs); gsl_spline *spline; }; someClass::someClass(const someClass& rhs){ //gsl_spline *spline; //was sucht diese lokale variable hier? // jetzt kopiere den Inhalt des Speicherblocks auf den rhs.spline zeigt in den eben angeforderten Speicherblock auf den spline zeigt. spline = new spline(rhs.spline); //hätte man fast selbst drauf kommen können //this->spline = new spline(rhs.spline); //tut dasselbe, leuchtet aber vielleicht mehr ein }
-
nwp3 schrieb:
Klaus82 schrieb:
class someClass{ someClass(const someClass& rhs); gsl_spline *spline; }; someClass::someClass(const someClass& rhs){ //gsl_spline *spline; //was sucht diese lokale variable hier? // jetzt kopiere den Inhalt des Speicherblocks auf den rhs.spline zeigt in den eben angeforderten Speicherblock auf den spline zeigt. spline = new spline(rhs.spline); //hätte man fast selbst drauf kommen können //this->spline = new spline(rhs.spline); //tut dasselbe, leuchtet aber vielleicht mehr ein }
Wohl kaum. Vielmehr
spline = new gsl_spline(*rhs.spline)
Ich hoffe doch, dass someClass auch noch den passenden Destructor bekommt. Oder besser den passenden Smart-Pointer für spline...
-
Laut Furble Wurble, hat gsl_spline aber selber kei.en Copyctor, sondern macht dann auch nur ei.e shallow copy.
Das heißt du hast zwei Möglichkeiten: Klasse moveonly machen oder Refcounting.
Es sei denn es gibt eine copyspline Funktion, die die richtige Kopie macht.
-
Nathan schrieb:
Laut Furble Wurble, hat gsl_spline aber selber kei.en Copyctor, sondern macht dann auch nur ei.e shallow copy.
Das heißt du hast zwei Möglichkeiten: Klasse moveonly machen oder Refcounting.
Es sei denn es gibt eine copyspline Funktion, die die richtige Kopie macht.Wir müssen zwei Dinge unterscheiden: Die gsl_spline-Objekte haben Pointer auf Objekte (x, y, interp), die sie selbst vermutlich nicht besitzen und die daher extern erzeugt und wieder zerstört werden müssen. (kenne jetzt den Hintergrund dieser Splines nicht).
Die letzte Frage des TO bezog sich jedoch auf die Klasse someClass, die Pointer auf gsl_spline-Objekte als Member hat. So wie er die Frage gestellt hat, will er nur die gsl_spline-Objekte kopieren, die ihrerseits dann auf die gleichen Internas zeigen (ob er das wirklich will, ist eine andere Frage). Das hängt alles davon ab, ob gsl_spline per Definition Ownership über die x,y,interp-Objekte übernehmen soll oder nicht. Wenn ja, ist ein noch tieferes Copy angezeigt, und dann kommen deine Überlegungen zum Tragen.
-
Nathan schrieb:
Laut Furble Wurble, hat gsl_spline aber selber kei.en Copyctor, sondern macht dann auch nur ei.e shallow copy.
Das heißt du hast zwei Möglichkeiten: Klasse moveonly machen oder Refcounting.
Es sei denn es gibt eine copyspline Funktion, die die richtige Kopie macht.Oder er baut sich halt einen Copy-Konstruktor für seine Spline-Struktur.
Ist zwar in Klassen etwas sexier, geht aber auch in Strukturen. Oder er tut das Ding in einen shared_ptr, falls er nicht wirklich eine Kopie braucht, sondern nur einen "Link".
-
Guten Morgen,
It0101 schrieb:
Oder er tut das Ding in einen shared_ptr, falls er nicht wirklich eine Kopie braucht, sondern nur einen "Link".
Nochmal zur Motivation: Ausgangspunkt ist
firstprivate
von Open MP, siehe hier.Ich versuche einen wrapper zu konzipieren, der einen Datensatz zur Interpolation beinhaltet, d.h. mittels des Datensatzes wird dieser
spline
initialisiert.Wenn ich
firstprivate
richtig verstehe wird ein Objekt 'in jeden Thread kopiert', d.h. jeder Thread hat seine eigene lokale Kopie. In der Beschreibung vonfirstprivate
steht, dass dazu der Copyconstructor des Objekts aufgerufen wird - und an diesem Punkt stehe ich gerade: Ich verstehe das so, dass quasi in jedem Thread ein ihm eigenes Objekt über den Copykonstruktor initialisiert wird, d.h. jedes kopierte Objekt ist komplett selbst für seine Inhalte verantwortlich.Aus dem Grund denke ich auch, dass Referenzen ein Widerspruch sind, denn ich will nicht in jedem Thread ein Objekt, das über Referenzen auf das Ursprungsobjekt zugreift.
Also benötige ich einen Move Constructor?
Gruß,
-- Klaus.
-
Die GSL bietet keine Funktion für eine tiefe Kopie eines
gsl_spline
-Objekts an. Aber das wäre genau das, was du willst (bzw zu wollen glaubst). Stattdessen musst Du wohl mitgsl_spline_alloc
undgsl_spline_init
arbeiten. Und dazu müsste man dann das Fucking Manual lesen.
-
So,
ich denke ich habe es jetzt:Der Kopierkonstruktor kann leider nicht kopieren, aber mit dem Inhalt des zu kopierenden Objekts bin ich in der Lage im neuen Objekt neu zu initialisieren.
Anbei noch der move constructor und move assignment operator.
Jetzth habe ich doch aber alles, oder nicht?
Und die magische Zeile 57
acc(rhs.acc),spline(rhs.spline)
funktioniert nun, weil ich
interpolation
als Rvalue caste? Als Lvalue würde es einfach die Adresse des einen Pointers in den anderen schreiben./* interpolation.h */ #ifndef INTERPOLATION_H_ #define INTERPOLATION_H_ #include <iostream> #include <vector> #include <gsl/gsl_spline.h> #include <gsl/gsl_errno.h> struct interpolation { interpolation(); interpolation(std::vector<double>, std::vector<double>); interpolation(const interpolation&); interpolation& operator=(const interpolation&); interpolation& operator=(interpolation&&); interpolation(interpolation&&); ~interpolation(); double operator()(double); std::vector<double> x,y; gsl_interp_accel *acc; gsl_spline *spline; }; #endif
/* interpolation.cpp */ #include <iostream> #include "interpolation.h" /* ---{}--- default constructor ---{}--- */ interpolation::interpolation():x(),y(), acc(nullptr),spline(nullptr) {} /* ---{}--- constructor ---{}--- */ interpolation::interpolation(std::vector<double> x, std::vector<double> y): x(x), y(y), acc(gsl_interp_accel_alloc()), spline(gsl_spline_alloc(gsl_interp_cspline,x.size())) { gsl_spline_init(spline,x.data(),y.data(),x.size()); } /* ---{}--- copy constructor ---{}--- */ interpolation::interpolation(const interpolation& rhs): x(rhs.x),y(rhs.y), acc(gsl_interp_accel_alloc()), spline(gsl_spline_alloc(gsl_interp_cspline,x.size())) { gsl_spline_init(spline,x.data(),y.data(),x.size()); } /* ---{}--- assignment operator ---{}--- */ interpolation& interpolation::operator=(const interpolation& rhs) { if( this != &rhs) { gsl_interp_accel_free(acc); gsl_spline_free(spline); x.clear(); y.clear(); x = rhs.x; y = rhs.y; acc = gsl_interp_accel_alloc(); spline = gsl_spline_alloc(gsl_interp_cspline,x.size()); gsl_spline_init(spline,x.data(),y.data(),x.size()); } return *this; } /* ---{}--- move constructor ---{}--- */ interpolation::interpolation(interpolation&& rhs): x(rhs.x),y(rhs.y), acc(rhs.acc),spline(rhs.spline) { rhs.acc = nullptr; rhs.spline = nullptr; } /* ---{}--- move assignment operator ---{}--- */ interpolation& interpolation::operator=(interpolation&& rhs) { if( this != &rhs) { gsl_interp_accel_free(acc); gsl_spline_free(spline); x.clear(); y.clear(); x = rhs.x; y = rhs.y; acc = gsl_interp_accel_alloc(); spline = gsl_spline_alloc(gsl_interp_cspline,x.size()); gsl_spline_init(spline,x.data(),y.data(),x.size()); rhs.x.clear(); rhs.y.clear(); gsl_interp_accel_free(rhs.acc); gsl_spline_free(rhs.spline); rhs.acc = nullptr; rhs.spline = nullptr; } return *this; } /* ---{}--- destructor ---{}--- */ interpolation::~interpolation() { gsl_interp_accel_free(acc); gsl_spline_free(spline); } /* ---{}--- operator() ---{}--- */ double interpolation::operator() (double x) { return gsl_spline_eval(spline,x,acc); }
Gruß,
-- Klaus.