Pointer



  • Klaus82 schrieb:

    Ich kann in Zeile 22 den Inhalt kopieren und in Zeile 26 die Adresse übergeben. Was mich wurmt: Wieso zeigen nun zwei Zeiger auf den Speicherbereich, für den der unique_ptr zuständig ist? Ich dachte genau das soll diser spezielle Pointer verhindern?

    Gruß,
    -- Klaus.

    Nein.
    Der Sinn von unique_ptr ist es, die Situation abzubilden, wo man einen Owner einer Resource hat. In diesem Fall ist die Resource dynamisch angeforderter Speicher. unique_ptr verhindert lediglich, dass man ihn kopieren kann und somit das Konzept eines einzelnen Owners umgeht.
    Es ist klar, dass man über den rohen Pointer Mist bauen kann. Das kann man auch kaum verhindern. Aber man muss nicht gleich den DAU spielen.
    Die rohen Pointer, die man via get() erhält, sollen nicht als besitzende Pointer agieren, das heißt den Speicher via delete oder ähnliches zu löschen. Der besitzender Pointer ist der unique_ptr und wenn der zerstört wird, wird die Resource freigegeben. Man muss dann natürlich aufpassen, dass man danach nicht mehr mit den get() Pointern auf die Resource zugreift. Deshalb am besten vermeiden get() aufzurufen.
    In modernem C++ solllte man generell selten rohe Pointer verwenden. Nur, wenn die auf etwas anderes zeigen sollen, nicht um Resourcen zu besitzen.
    Im Zusammenhang mit Resourcenownership verwendent man Smart Pointer.
    Und kannst du bitte aufhören memcopy zu verwenden? Die Funktion ist einfach nur böse.



  • Nathan_logoff schrieb:

    Und kannst du bitte aufhören memcopy zu verwenden? Die Funktion ist einfach nur böse.

    Die Funktion ist eigentlich in Ordnung ( jaja kein C++ bla bla ), aber nicht wie er sie dort verwendet 😃

    SmartPointer sind eigentlich in C++ das wichtigste überhaupt. Ohne die Dinger wären die STL-Container eine echte Performancebremse 😃


  • Mod

    SmartPointer sind eigentlich in C++ das wichtigste überhaupt. Ohne die Dinger wären die STL-Container eine echte Performancebremse

    Was meinst du denn damit?



  • Nathan_logoff schrieb:

    Und kannst du bitte aufhören memcopy zu verwenden?

    +1

    In dem gsl thread (http://www.c-plusplus.net/forum/316751) bist Du etwas auf die falsche Spur geschickt worden.
    Das Prinzip einer Deep Copy ist Dir ja bekannt. Und demensprechend sollte Dir auch einleuchten wie schnell man mit einer Shallow Copy in Teufels Küche kommen kann.



  • It0101 schrieb:

    Nathan_logoff schrieb:

    Und kannst du bitte aufhören memcopy zu verwenden? Die Funktion ist einfach nur böse.

    Die Funktion ist eigentlich in Ordnung ( jaja kein C++ bla bla ), aber nicht wie er sie dort verwendet 😃

    std::copy nutzt im release-build automatisch das beste Kopierverfahren, abhängig vom Typ. Und wenn std::copy memcopy für das beste hält, wird es verwendet (wobei ich glaube, dass eher memmove verwendet wird... hat glaub irgendwas damit zu tun, wenn der Zielspeicherplatz teil des Quellspeicherplatzes ist... das kann memcopy glaub nicht).



  • Klaus82, das "unique" in unique_ptr biezieht sich wie das "shared" in shared_ptr auf die Besitzverhältnisse. Ein unique_ptr fühlt sich alleinig verantwortlich für die Löschung des referenzierten Objekts (unique ownership). Ein shared_ptr teilt sich diesen Besitz (shared ownership) mit anderen shared_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 per delete . In Zeile 11 überschreibst du die Adresse des von dir per new angelegten int-Objekts mit der Adresse des Objekts, auf das auch uptr zeigt. Du kannst das Objekt also nicht mehr per delete 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 kein release() aufrufst, irgendwann per delete selbst löschen, entweder im Destruktor, im Zuweisungsoperator oder per reset .



  • 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 jetzt memcpy 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 oder spline ) 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 oder spline ) 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 ein gsl_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, denn gsl_spline benöigt. Jetzt fange bei der Adresse von rhs.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 mittels memcpy oder copy 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 von firstprivate 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 mit gsl_spline_alloc und gsl_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.


Anmelden zum Antworten