Ist es sicher den this-Pointer zu speichern?



  • Hallo,

    angenommen ich will eine Baumstruktur erstellen, dann muss ich ja in jedem Knoten einen Pointer auf den "parent" des aktuellen Knotens speichern. Vom Prinzip her mache ich das so:

    #include <memory>
    #include <vector>
    #include <iostream>
    
    template <typename T>
    struct node
    {
    	node *parent;
    	std::vector<std::shared_ptr<node>> children;
    	std::shared_ptr<T> data;
    
    	node() {}
    	template <typename data_type>
    	explicit node(data_type const &data) : data(std::make_shared<data_type>(data)) {}
    
    	node *add_child(T data) { 
    		children.push_back(std::make_shared<node>(data));
    		children.back()->parent = this; // Frage: sicher?
    		return children.back().get();
    	}
    };
    
    int main()
    {
    	node<int> n;
    	n.add_child(1);
    	n.add_child(2);
    
    	for (auto const &i : n.children) {
    		std::cout << *(i->data) << '\n'; // Output: 1 2
    	}
    }
    

    Dazu jetzt meine Frage, ist das so sicher (laufen tut es zumindest so wie es soll, aber das muss ja nichts heißen)? Oder kann sich der this-Pointer verändern, obwohl das Objekt noch lebt? Und, ist das prinzipiell so Ok oder würde man das anders machen?


  • Mod



  • shared_ptr ist nicht der passende Smartpointer für eine Baumstruktur.



  • Arcoth schrieb:

    Suchst du enable_shared_from_this ?

    Hm ich glaub nicht, also ich will ja keinen shared_ptr aus add_child zurückgeben sondern einen normalen (rohen) pointer, oder verstehe ich deinen Vorschlag jetzt falsch?

    baumstruktur schrieb:

    shared_ptr ist nicht der passende Smartpointer für eine Baumstruktur.

    Sondern welcher dann? unique_ptr?


  • Mod

    happystudent schrieb:

    baumstruktur schrieb:

    shared_ptr ist nicht der passende Smartpointer für eine Baumstruktur.

    Sondern welcher dann? unique_ptr?

    Ja, entweder unique_ptr, wenn du sagst, dass die Kinder ihren Eltern gehören. Oder ganz normale, rohe Zeiger, wenn du sagst, dass die Knoten dem Baum gehören. shared_ptr ist hier unpassend. Was soll das hier genau modellieren und wann sollen jemals die Eigenschaften eines shared_ptr hier eine Rolle spielen? Sollen mehrere Eltern das gleiche Kind besitzen können?

    Zu deiner eigentlichen Frage: make_shared nutzt intern (oder verhält sich so als ob) den globalen Operator new. Ein so erstelltes Objekt verändert nicht einfach so seine Adresse.



  • SeppJ schrieb:

    Ja, entweder unique_ptr, wenn du sagst, dass die Kinder ihren Eltern gehören. Oder ganz normale, rohe Zeiger, wenn du sagst, dass die Knoten dem Baum gehören. shared_ptr ist hier unpassend. Was soll das hier genau modellieren und wann sollen jemals die Eigenschaften eines shared_ptr hier eine Rolle spielen? Sollen mehrere Eltern das gleiche Kind besitzen können?

    Ok, alles klar dann werd ich auf unique_ptr umsteigen.

    SeppJ schrieb:

    Zu deiner eigentlichen Frage: make_shared nutzt intern (oder verhält sich so als ob) den globalen Operator new. Ein so erstelltes Objekt verändert nicht einfach so seine Adresse.

    Ok, das ist auf jeden Fall gut zu wissen, danke 🙂



  • Ich bin anderer Meinung. Ein Baum ist eine rekursive Datenstruktur.
    Ich hatte sehr wohl schon den Anwendungsfall Unterbäume herumreichen und speichern zu wollen und habe da den Baum von unique_ptr auf shared_ptr umgestellt.


  • Mod

    Wobei unique_ptr hier nur bedingt einsatzfähig ist.
    In Bezug auf node::data ist nicht klar, wieso hier überhaupt Indirektion erforderlich ist, ein unique_ptr wäre allerdings möglich.
    node::children kann allerdings kein std::vector<std::unique_ptr<node>> sein ohne UB hevorzurufen.
    Im Prinzip ist auch für node::children die doppelte Indirektion (1 Indirektion wird ja schon durch vector impliziert) überflüssig, mit ein bisschen Aufwand (wie z.B. hier) kann man diese ebenso vermeiden, und so ganz auf Zeiger (sowohl roh als auch smart) verzichten.



  • Ethon schrieb:

    Ich hatte sehr wohl schon den Anwendungsfall Unterbäume herumreichen und speichern zu wollen und habe da den Baum von unique_ptr auf shared_ptr umgestellt.

    Und alle Unterbäume können den Baum überleben?
    Dann hast du den Baum hoffentlich immutable gemacht.



  • camper schrieb:

    Wobei unique_ptr hier nur bedingt einsatzfähig ist.
    In Bezug auf node::data ist nicht klar, wieso hier überhaupt Indirektion erforderlich ist, ein unique_ptr wäre allerdings möglich.

    Das hab ich gemacht, weil ich einen Baum von abstrakten Objekten brauche, also was wie tree<my_abstract_object> .

    camper schrieb:

    node::children kann allerdings kein std::vector<std::unique_ptr<node>> sein ohne UB hevorzurufen.

    Hä, warum dass?

    camper schrieb:

    Im Prinzip ist auch für node::children die doppelte Indirektion (1 Indirektion wird ja schon durch vector impliziert) überflüssig, mit ein bisschen Aufwand (wie z.B. hier) kann man diese ebenso vermeiden, und so ganz auf Zeiger (sowohl roh als auch smart) verzichten.

    Aber da verwende ich doch auch shared_ptr ?


  • Mod

    Template-Argumente fuer Templates in der Standardbibliothek (smart pointer eingeschlossen) duerfen i.d.R. Keine unvollstaendigen Typen sein.



  • Arcoth schrieb:

    Template-Argumente an Templates in der Standardbibliothek (smart pointer eingeschlossen) duerfen i.d.R. nicht unvollstaendig sein.

    Wie jetzt, hier wurde mir aber gesagt dass smart pointer hier eine Ausnahme sind. Wie soll man denn sonst unvollständige Typen mit smart pointern speichern und was stimmt jetzt hier? Oder gilt das jetzt nur für smart_ptr und nicht für unique_ptr ? Warum wäre dass dann anders als bei smart_ptr ?


  • Mod

    Ja, hast natürlich Recht.

    [unique.ptr]/5 schrieb:

    The template parameter T of unique_ptr may be an incomplete type.

    Ich hatte flugs in der nachvollziehbaren Annahme geantwortet dass camper keine Flüchtigkeitsfehler mache. (Außerdem war ich auf dem Handy und konnte nicht den Standard checken. :p )

    Warum wäre dass dann anders als bei smart_ptr ?

    Weil shared_ptr etwas anderes ist als unique_ptr . Ich nehme mal an es müssen einige interne Deklarationen instantiiert werden die die Vollständigkeit von T voraussetzen.


  • Mod

    ... Ich wusste ich hätt's prüfen sollen.

    [util.smartptr.shared]/2 schrieb:

    The template parameter T of shared_ptr may be an incomplete type.



  • baumstruktur schrieb:

    Ethon schrieb:

    Ich hatte sehr wohl schon den Anwendungsfall Unterbäume herumreichen und speichern zu wollen und habe da den Baum von unique_ptr auf shared_ptr umgestellt.

    Und alle Unterbäume können den Baum überleben?
    Dann hast du den Baum hoffentlich immutable gemacht.

    In meinem Fall ging es um einen AST, der interpretiert wurde. Unterbäume wurden durch Opimierungen modifiziert, dass diese nur 1x für beliebig viele Kopien durchgeführt wurden war sogar erwünscht.


  • Mod

    Arcoth schrieb:

    Ja, hast natürlich Recht.

    [unique.ptr]/5 schrieb:

    The template parameter T of unique_ptr may be an incomplete type.

    Ich hatte flugs in der nachvollziehbaren Annahme geantwortet dass camper keine Flüchtigkeitsfehler mache. (Außerdem war ich auf dem Handy und konnte nicht den Standard checken. :p )

    Huh, keine Ahnung wie miur das entgehen konnte... aber wäre nicht das erste mal, dass ich Unfug erzähle und wird auch nicht das letzte mal gewesen sein. Also schön wachsam bleiben 🕶



  • camper schrieb:

    Arcoth schrieb:

    Ja, hast natürlich Recht.

    [unique.ptr]/5 schrieb:

    The template parameter T of unique_ptr may be an incomplete type.

    Ich hatte flugs in der nachvollziehbaren Annahme geantwortet dass camper keine Flüchtigkeitsfehler mache. (Außerdem war ich auf dem Handy und konnte nicht den Standard checken. :p )

    Huh, keine Ahnung wie miur das entgehen konnte... aber wäre nicht das erste mal, dass ich Unfug erzähle und wird auch nicht das letzte mal gewesen sein. Also schön wachsam bleiben 🕶

    Kennt ihr beide nicht das neue "idiomatische" Pimpl Idiom?

    class foo
    {
       struct impl;
       std::unique_ptr<impl> pimpl;
    public:
       ~foo(); // destruktor muss dann natürlich in cpp definiert werden...
    };
    


  • Nathan schrieb:

    Kennt ihr beide nicht das neue "idiomatische" Pimpl Idiom?

    class foo
    {
       struct impl;
       std::unique_ptr<impl> pimpl;
    public:
       ~foo(); // destruktor muss dann natürlich in cpp definiert werden...
    };
    

    Jetzt nur noch die beiden Kopierkonstruktoren und den einen Zuweisungsoperator definieren.



  • plimpl schrieb:

    Nathan schrieb:

    Kennt ihr beide nicht das neue "idiomatische" Pimpl Idiom?

    class foo
    {
       struct impl;
       std::unique_ptr<impl> pimpl;
    public:
       ~foo(); // destruktor muss dann natürlich in cpp definiert werden...
    };
    

    Jetzt nur noch die beiden Kopierkonstruktoren und den einen Zuweisungsoperator definieren.

    Jup.



  • Und das reicht immer noch nicht, wenn man die Klasse aus einer Dll exportieren will, was bei einer Klasse mit Pimpl nicht unwahrscheinlich ist.


  • Mod

    Nathan schrieb:

    Kennt ihr beide nicht das neue "idiomatische" Pimpl Idiom?

    class foo
    {
       struct impl;
       std::unique_ptr<impl> pimpl;
    public:
       ~foo(); // destruktor muss dann natürlich in cpp definiert werden...
    };
    

    Macht in der Praxis kaum jemand so. Da in aller Regel sowieso noch Kopieren und Zuweisung definiert wird, ist der Nutzen gering.
    (Die Verwendung von unique_ptr erspart copy-ctor und -Opeerator sofern die Semantik stimmt; aber diese Verletzung der Regel der Drei bedarf im Prinzip schon wieder eines Kommentares der die Ersparnis negiert).

    Und früher zu auto_ptr-Zeiten war das eben undefiniert.


Anmelden zum Antworten