Unique_ptr in STL-Container



  • Ich habe eine Liste mit unique_ptr auf Foo Objekte und möchte nach und nach dynamisch allozierte Objekte der Liste hinzufügen

    Unique_ptr erlaubt keine Kopien, aber push_back nimmt doch auch eine Referenz. Weshalb funktioniert also folgendes nicht?

    std::list<unique_ptr<Foo>> list;
    
    		auto s = make_unique<Foo>();
    		list.push_back(s);
    

    Schreibe ich hingegen

    std::list<unique_ptr<Foo>> list;
    		list.push_back(make_unique<Shot>());
    

    erkennt der Compiler, dass das unnamed Objekt im Methodenaufruf temporary ist und lässt es zu



  • Sewing schrieb:

    Weshalb funktioniert also folgendes nicht?

    std::list<unique_ptr<Foo>> list;
    auto s = make_unique<Foo>();
    list.push_back(s);
    

    Weil push_back(const T&) intern eine Kopie des Elements machen möchte, was aber bei std::unique_ptr<..> nicht geht.

    Sewing schrieb:

    Schreibe ich hingegen

    std::list<unique_ptr<Foo>> list;
    list.push_back(make_unique<Shot>());
    

    erkennt der Compiler, dass das unnamed Objekt im Methodenaufruf temporary ist und lässt es zu

    Genau, und das Temporary ist ein R-Value, und dafür gibt es die Überladung push_back(T&&) .

    Im ersten Beispiel kannst du mit std::move(..) dem Kompiler mitteilen, dass du das Objekt s nicht mehr brauchst nachdem du es in die std::list<..> gesteckt hast.

    std::list<unique_ptr<Foo>> list;
    auto s = make_unique<Foo>();
    list.push_back(std::move(s));
    


  • danke theta für die Hilfe, was ist denn Schneller?

    Werden also in diesem Falle, dh. bei Smart Pointers in Container, immer temporaries im push_back verwendet oder was ist da das gängige Vorgehen?



  • Sewing schrieb:

    temporaries im push_back verwendet oder was ist da das gängige Vorgehen?

    Der unique_ptr hat halt keine Kopierkonstrukturen, das ist eigentlich schon der ganze Spuk. Seit C++11 gibt es push_back()-Methoden, die Rvalue-Referenzen als Parameter erwarten, so wird also intern keine Kopie mehr von der Instanz angelegt. Daher vec.push_back(std::move(x)) um die push_back(T&& ) Variante aufzurufen.



  • und was ist effizienter?

    ich behelfe mir gerade, indem ich in Methoden Referenzen auf Pointer verwende. Ist das in Ordnung oder wir das normal anders gemacht?

    void someMethod(unique_ptr<Foo> &f)
    


  • Das sollte eigentlich keinen Unterschied machen. Es würde mich nicht wundern, wenn da derselbe Maschinencode bei rauskommt.

    Aber warum will man eine list<unique_ptr<T>> haben? Weißt Du, wie sowas im Speicher aussieht? Warum nicht vector<unique_ptr<T>> oder deque<T> oder irgendwas anderes? Die Kombination list mit unique_ptr kommt mir doch sehr komisch vor.

    Du weißt, dass list eine doppelt-verkettete Liste und keine Art von Array ist, wie man das vielleicht aus Python kennt, oder?

    Sewing schrieb:

    void someMethod(unique_ptr<Foo> &f)
    

    Ob das so sinnvoll ist, hängt davon ab, was deine ursprüngliche Motivation war. Ich tendiere aber zu "Nein". Rein von der Signatur her, sagt mir das, dass die Funktion den unique_ptr des Aufrufers verändern soll. Aber das ist wahrscheinlich nicht das, was Du vermitteln willst. Wenn die Funktion nur irgendwas kurz mit dem Foo-Objekt machen soll, dann würde ich es so schreiben:

    void someMethod1(Foo const& f) // falls lesender Zugriff reicht
    void someMethod2(Foo & f)      // sonst
    

    Wenn die Methode den Besitz des Foo-Objektes übernehmen soll, dann so:

    void someMethod3(unique_ptr<Foo> f) // pass-by-value ist hier Absicht
    

    Hier findet jetzt eine Besitzübergabe statt. Die Funktion hat ihren eigenen unique_ptr, der das Foo-Objekt dann managed.

    Auf Maschinenebene ist so ein unique_ptr mit Default-Lösch-Funktor fast so billig wie ein roher Zeiger. Dieser Schlauzeiger zieht sehr effizient von A nach B um. Der einzige Unterschied ist, dass dabei der Quellzeiger genullt wird. Sonst passiert da nicht mehr als bei einer Kopie eines rohen Zeigers.



  • Danke erstmal Krümel 🙂

    Aber dein letzter Code funktioniert doch so nicht,, denn es wird ja eine Kopie angefordert des unique_ptr oder nicht? Deshalb hatte ich an der Stelle eine Referenz angelegt.

    Was genau ist an der List hier so schlimm?

    Aus der Liste werden im Laufe des Programms an zufälligen Stellen Elemente entfernt. Ist da der Vector tatsächlich besser?

    Was someMethod1 nd someMethod2 angeht verstehe ich deine Intention. Allerdings habe ich nunmal unique_ptr in meiner list, oder meinst du, die werden bei Methodenaufruf dann dereferenziert?



  • Du könntest auch einfach emplace_back() verwenden.



  • Sorry, Sewing, ich habe deine Antwort übersehen.

    Sewing schrieb:

    Aber dein letzter Code funktioniert doch so nicht, denn es wird ja eine Kopie angefordert des unique_ptr oder nicht?

    Nein. Das ist ja das coole an Move-Semantik:

    #include <iostream>
    #include <memory>
    #include <cassert>
    
    using namespace std;
    
    unique_ptr<int> quelle() {
        unique_ptr<int> p (new int(23));
        return p; // hier wird nix kopiert
    }
    
    void senke(unique_ptr<int> p) {
        cout << *p << '\n';
    }
    
    int main() {
        senke(quelle()); // hier wird nix kopiert
        auto tmp = quelle();
        //senke(tmp); // Kopierversuch, da tmp ein Lvalue ist
        senke(std::move(tmp)); // hier wird nix kopiert
        // tmp ist nach senke::p umgezogen.
        assert(tmp == nullptr);
        return 0;
    }
    

    Sewing schrieb:

    Deshalb hatte ich an der Stelle eine Referenz angelegt.

    Ja, und das ist unpraktisch. Was nützt dir eine Referenz auf den unique_ptr<T> , wenn Du eigentlich an dem T interessiert bist? Das Entgegennehmen von einer Referenz auf einen unique_ptr<T> schränkt deine Funktion nur unnötig ein. Der einzige Grund, der mir einfällt, wo das sinnvoll sein könnte, ist der, dass die Funktion ggf den unique_ptr updaten will, so dass der Aufrufer davon etwas mitbekommt.

    Sewing schrieb:

    Was genau ist an der List hier so schlimm?

    Das Speicherlayout von list<unique_ptr<T>> ist relativ schlecht. Du hast sowohl die T s als auch die einzelnen Listenknoten separat irgendwo im Speicher stehen. Wenn Du über die T s iterieren willst, springst Du unnötig viel im Speicher hin und her: Einen Hop für den nächsten Listenknoten und noch einen Hop um zum T zu kommen. Das mag der Cache gar nicht. Je nachdem, was Du machen willst, kommen sicherlich auch bessere andere Ansätze in Frage.

    Sewing schrieb:

    Aus der Liste werden im Laufe des Programms an zufälligen Stellen Elemente entfernt. Ist da der Vector tatsächlich besser?

    Wie kommst du denn an diese zufälligen Stellen ran? Merkst Du dir die Iteratoren irgendwo, oder musst Du die Elemente erst wieder suchen?

    So oder so: Probier's doch aus.

    Sewing schrieb:

    Was someMethod1 nd someMethod2 angeht verstehe ich deine Intention. Allerdings habe ich nunmal unique_ptr in meiner list

    Na und? Und: Warum eigentlich?

    Sewing schrieb:

    oder meinst du, die werden bei Methodenaufruf dann dereferenziert?

    Ich verstehe nicht, wo das Problem liegt. Wenn du einen unique_ptr<X> hast und eine Funktion aufrufen willst, die ein X& oder X const& entgegen nimmt, dann dereferenzierst Du deinen unique_ptr einfach.



  • Danke Krümelacker für dien ausführliche Antwort.

    Bin gerade nicht im Lande, um mir das mal näher anzusehen, komme aber bald drauf zurück und werde Rückmeldung geben


Anmelden zum Antworten