Boost Smart Pointer jetzt Standard C++?



  • Abend,

    es gibt ja in der Boost Lib einige Smart Pointer wie z.B. shared_ptr<T>. Sind die jetzt eigentlich im ganz normalen C++ Standard drinnen? Oder muss man nach wie vor boost installieren?



  • Soweit ich weiss, ist std::shared_ptr der einzige aus Boost, der in die Standardbibliothek von C++0x kommt. Zusätzlich haben wir std::unique_ptr .

    Warum scoped_ptr weder im TR1 noch in C++0x ist, verstehe ich nicht.



  • Was wäre den der Vorteil eines scoped_ptr gegenüber eines auto_ptr ?



  • Nicht deprecated :p

    Nein, der Vorteil gegenüber auto_ptr wäre, das keine implizite Move-Semantik eingebaut ist. Doch jetzt fällt mir auch wieder ein, dass unique_ptr die Funktionalität von scoped_ptr und auto_ptr vereint. Alles doch nicht so schlimm 🙂



  • Hat man im VS2010 eigentlich bereits C++0x?



  • Zum Teil, siehe hier. Unter anderem Lambda-Ausdrücke, RValue-Referenzen und Typinferenz ist unter Visual Studio 2010 vorhanden.

    Bibliotheksfeatures sind weniger das Problem, sobald die basierenden Sprachfeatures verfügbar sind.



  • Das, was scoped_ptr kann, kann auch unique_ptr. Aber nicht umgekehrt. scoped_ptr hat keine Move-Semantik. Der einzige Vorteil von scoped_ptr ist also der, dass wenn man Angst hat, Move-Semantik versehentlich zu nutzen, ein solcher Fehler nur bei scoped_ptr auffallen würde. Aber ich denke nicht, dass das ein Problem ist. unique_ptr vereint sogar die Funktionalität von scoped_ptr und scoped_array:

    int main() {
      unique_ptr<int[]> p (new int[123]);
      unique_ptr<doube> q (new double);
      p[17] = 29;
      *q = 3.14159265;
    }
    

    Ich finde, mit dem, was die C++0x Lib bietet bzgl schlauen Zeigern, kann man eigentlich zufrieden sein. Es gibt öfters Momente, in denen ich denke "unique_ptr könnte hier jetzt praktisch sein". std::shared_ptr ist ja auch ganz nett, aber das brauche ich so gut wie nie.

    Dann gibt es in Boost, glaube ich, noch einen intrusive_ptr, wobei der Referenzzähler vom Objekt selbst bereitgestellt wird. Soviel besser ist das aber auch nicht im Vergleich zu shared_ptr, da die C++0x Lib das Funktionstemplate "make_shared" anbieten wird, welches eine Schwäche von shared_ptr ohne make_shared gegenüber intrusive_ptr umgeht: nämlich die Notwendigkeit, zwei Allozierungen durchzuführen .... einmal für das Objekt und einmal für den Referenzzähler & co. Das kann make_shared nämlich in einem Rutsch erledigen, wobei Objekt und Referenzzähler im selben Speicherblock abgelegt werden (also fast wie intrusive_ptr, ohne dass man etwas dafür tun muss) 😉

    int main() {
      auto sp = make_shared<string>("hello world!");
      // decltype(sp) --> shared_ptr<string>
      cout << *sp << endl;
    }
    

    Aber leider gibt es so etwas nicht für unique_ptr, kann man sich aber selbst bauen, wenn man will:

    template<class T, class...Args>
    unique_ptr<T> make_unique(Args&&...args)
    {
      unique_ptr<T> result (new T(std::forward<Args>(args)...));
      return result;
    }
    


  • template<class T, class...Args>
    unique_ptr<T> make_unique(Args&&...args)
    {
      unique_ptr<T> result (new T(std::forward<Args>(args)...));
      return result;
    }
    

    [/quote]
    Respekt. Die haben es echt geschafft eine eh schon sehr komplexe Sprache noch unleserlicher zu machen.

    👎



  • Versteh ich nicht. Wieso ist das unleserlich? Wenn man die Einzelteile versteht, ist das gut leserlich. Und ich mag das 0x-Zeug sehr.



  • Kann mir jemand einen Link geben wo ich gute Informationen zu C++0x bekomme?



  • icarus2 schrieb:

    Kann mir jemand einen Link geben wo ich gute Informationen zu C++0x bekomme?

    Dort ist alles wichtige zusammengefasst:
    http://www2.research.att.com/~bs/C++0xFAQ.html



  • Athar schrieb:

    icarus2 schrieb:

    Kann mir jemand einen Link geben wo ich gute Informationen zu C++0x bekomme?

    Dort ist alles wichtige zusammengefasst:
    http://www2.research.att.com/~bs/C++0xFAQ.html

    Danke dir. Werde morgen mal anfangen zu lesen 🙂



  • C++0x Enttäuschter schrieb:

    template<class T, class...Args>
    unique_ptr<T> make_unique(Args&&...args)
    {
      unique_ptr<T> result (new T(std::forward<Args>(args)...));
      return result;
    }
    

    Respekt. Die haben es echt geschafft eine eh schon sehr komplexe Sprache noch unleserlicher zu machen.

    Als DAU wirst Du solchen Code kaum sehen müssen. Viele der neuen "core language features" sind hauptsächlich dazu da, die Werkzeugkiste von Entwicklern von generischen Bibliotheken zu erweitern, so dass diverse Bibliotheks-Features elegant implementiert werden können. Im obigen Beispiel sind die Stichwörter "variadic templates" und "perfect forwarding". Perfect forwarding braucht man zB bei std::function und std::bind. Variadic templates ebenso. Du willst nicht wissen, was für ein Aufwand nötig ist, std::bind ohne diese Features zu implementieren/approximieren.

    Einige der Spracherweiterungen erhöhen auch die Uniformität, was bedeutet, dass es weniger Sonderregeln geben wird. Zumindest war das die Strategie um C++0x für Anfänger leichter zu machen. Als Beispiel wird hier gerne B.S. zu "uniform initialization" zitiert:

    vector<int> primes = {2,3,5,7,11};
    


  • C++0x Enttäuschter schrieb:

    template<class T, class...Args>
    unique_ptr<T> make_unique(Args&&...args)
    {
      unique_ptr<T> result (new T(std::forward<Args>(args)...));
      return result;
    }
    

    Respekt. Die haben es echt geschafft eine eh schon sehr komplexe Sprache noch unleserlicher zu machen.

    👎

    Im Vergleich zum üblichen STL Code ist es sehr lesbar 🙂



  • krümelkacker schrieb:

    Ich finde, mit dem, was die C++0x Lib bietet bzgl schlauen Zeigern, kann man eigentlich zufrieden sein.

    Ja, unique_ptr ist recht cool. Mir reicht das aber nicht immer, deshalb hab ich noch meine eigene SmartPointer-Werzeugkiste. Zum Beispiel gibt es fast nirgends vernünftige Implementierungen von kopierenden Smart-Pointern.

    Mir ist manchmal auch wichtig, dass der Pointee-Typ bei der Deklaration nicht bekannt sein muss. shared_ptr kann das, trifft das auf unique_ptr auch zu?

    krümelkacker schrieb:

    Einige der Spracherweiterungen erhöhen auch die Uniformität, was bedeutet, dass es weniger Sonderregeln geben wird.

    Das ist einer der Punkte, die mich an C++0x am meisten stören: Diese "Uniformität", nun 4 Schreibweisen für das Gleiche zu haben.

    int a = 4;
    int a(4);
    int a = {4};
    int a {4};
    

    Mindestens auf die letzte hätte man meiner Meinung nach ganz verzichten können. Und besonders mühsam wirds, wenn das Gleiche komplett Unterschiedliches bedeutet.

    std::complex<double> c {3.2, 4.5}; // Ruft den Konstruktor mit 2 Argumenten auf
    std::vector<double> v {3.2, 4.5}; // Initialisierungsliste für vector!
    

    Ich hoffe mal, ich bin immer noch auf dem aktuellen Stand. Falls das wirklich stimmt, haben sich die Leute dabei nichts überlegt? Zum Beispiel dass normaler Code mit Konstruktoraufrufe durch {} plötzlich eine ganz andere Bedeutung bekommt, wenn ein Initialisierungslisten-Konstruktor hinzugefügt wird?



  • Nexus schrieb:

    [...]Mindestens auf die letzte hätte man meiner Meinung nach ganz verzichten können. Und besonders mühsam wirds, wenn das Gleiche komplett Unterschiedliches bedeutet.

    std::complex<double> c {3.2, 4.5}; // Ruft den Konstruktor mit 2 Argumenten auf
    std::vector<double> v {3.2, 4.5}; // Initialisierungsliste für vector!
    

    [...]

    Tut es doch nicht. In beiden Fällen initialisierst Du ein Objekt bei der Konstruktion mit zwei Werten. Aus Abstraktionssicht geht es kaum besser.

    Was meinst Du mit kopierendem Smartpointer? Ein Smartpointer, der eine tiefe Kopie erzeugt?



  • Nexus schrieb:

    krümelkacker schrieb:

    Ich finde, mit dem, was die C++0x Lib bietet bzgl schlauen Zeigern, kann man eigentlich zufrieden sein.

    Ja, unique_ptr ist recht cool. Mir reicht das aber nicht immer, deshalb hab ich noch meine eigene SmartPointer-Werzeugkiste. Zum Beispiel gibt es fast nirgends vernünftige Implementierungen von kopierenden Smart-Pointern.

    Da habe ich auch erst kürzlich wieder darüber nachgedacht. Eigentlich könnte man das unique_ptr-Design dahingehend erweitern, dass man noch über einen "cloner" sagen kann, wie ein Objekt geklont werden soll. Der default_cloner könnte versuchen, eine clone-Funktion aufzurufen (falls vorhanden) und auf den Kopierkonstruktor zurückfallen. Beides könnte man optional halten, so dass unique_ptr weiterhin mit Typen, die nicht clone/kopierbar sind, nutzbar bleibt. Einen Ansatz dazu hatte ich auch schonmal:

    struct use_copy_ctor {};
    struct prefer_clone : use_copy_ctor {};
    
    #define AUTO_FUNC_RETURN(...) ->decltype(__VA_ARGS__) {return __VA_ARGS__;}
    
    template<class T>
    auto clone(T const* ptr, prefer_clone)
    AUTO_FUNC_RETURN(ptr->clone())
    
    template<class T>
    auto clone(T const* ptr, use_copy_ctor)
    AUTO_FUNC_RETURN(new T(*ptr))
    
    template<class T>
    struct default_cloner
    {
      T* operator()(T const* ptr) const {
        return clone(ptr,prefer_clone());
      }
    };
    

    Aber der GCC meldet im Moment noch viele Ausdrücke, die innerhalb ->decltype(...) stehen, als harten Fehler, obwohl hier eigentlich auch SFINAE greift. Mein Test mit T als abstrakter Klasse funktionierte hier noch nicht. Aber clang++ kann damit schon umgehen. 🙂

    Nexus schrieb:

    Mir ist manchmal auch wichtig, dass der Pointee-Typ bei der Deklaration nicht bekannt sein muss. shared_ptr kann das, trifft das auf unique_ptr auch zu?

    Mit "nicht bekannt" meinst Du "unvollständig" oder? unique_ptr unterstützt explizit unvollständige Typen. Man muss nur entweder einen eigenen Deleter verwenden oder dafür sorgen, dass Destruktor, reset und Zuweisungsoperator höchstens dort instantiiert werden, wo T dann schließlich vollständig bekannt ist. Letzteres kann man aber eigentlich nur erreichen, wenn man eine solche unique_ptr-Klasse nur als Typ eines privaten Klassenelements verwendet (zB für PImpl). Eine gute Implementierung sollte zumindest so etwas wie checked_delete im default_deleter verwenden, so dass im Falle einer Fehlbenutzung von unique_ptr es einen Compile-Zeit-Fehler gibt.

    Nexus schrieb:

    Das ist einer der Punkte, die mich an C++0x am meisten stören: Diese "Uniformität", nun 4 Schreibweisen für das Gleiche zu haben.

    int a = 4;
    int a(4);
    int a = {4};
    int a {4};
    

    Mindestens auf die letzte hätte man meiner Meinung nach ganz verzichten können. Und besonders mühsam wirds, wenn das Gleiche komplett Unterschiedliches bedeutet.

    std::complex<double> c {3.2, 4.5}; // Ruft den Konstruktor mit 2 Argumenten auf
    std::vector<double> v {3.2, 4.5}; // Initialisierungsliste für vector!
    

    Ist das nicht intuitiver so? Wenn das nicht viele als intuitiv empfunden hätten, hätte man sich die initializer_list-Konstruktoren für Container-Klassen auch gespart, denke ich. Man muss sich nur merken, dass, wenn man den anderen Konstruktor aufrufen will, die Klammern nutzen muss:

    std::vector<double> x {4,2}; // Vektor mit Elementen 4,2
    std::vector<double> x (4,2); // Vektor mit Elementen 2,2,2,2
    

    Das sehe ich jetzt aber nicht als Schwäche der Sprache C++, sondern höchstens als Geschmackssache bzgl des Klassendesigns.

    Nexus schrieb:

    Ich hoffe mal, ich bin immer noch auf dem aktuellen Stand. Falls das wirklich stimmt, haben sich die Leute dabei nichts überlegt? Zum Beispiel dass normaler Code mit Konstruktoraufrufe durch {} plötzlich eine ganz andere Bedeutung bekommt, wenn ein Initialisierungslisten-Konstruktor hinzugefügt wird?

    Wieso "plötzlich eine ganz andere Bedeutung"? Diese Art der Initialisierung für nicht-Aggregat-Klassen gab es doch vorher gar nicht. Es zwingt dich ja keiner, nur noch die geschweiften Klammern zu nutzen.

    Und selbst bei Aggregaten können wir jetzt statt

    aggregat_typ x = {...};
    return x;
    

    auch einfach

    return aggregat_typ {...};
    

    schreiben. Und das ist gut soTM. 🙂



  • Tachyon schrieb:

    Was meinst Du mit kopierendem Smartpointer? Ein Smartpointer, der eine tiefe Kopie erzeugt?

    Genau. Bin ich der einzige, der das je braucht? 😉

    krümelkacker schrieb:

    unique_ptr unterstützt explizit unvollständige Typen. Man muss nur entweder einen eigenen Deleter verwenden oder dafür sorgen, dass Destruktor, reset und Zuweisungsoperator höchstens dort instantiiert werden, wo T dann schließlich vollständig bekannt ist.

    Das heisst, man muss aber recht viel tun, um unvollständige Typen zu unterstützen? Denn im Deleter darf man ja nicht direkt ein delete stehen haben, weil das wieder vollständige Typen erfordert (um korrekt zu zerstören)...

    Ich verwende bei meiner Implementierung Funktionszeiger. Für den Anwender ist es dabei viel einfacher, unvollständige Pointee-Typen zu haben (dafür büsst man natürlich ganz wenig Performance ein, was aber durch new und delete eh meistens irrelevant ist).



  • Tachyon schrieb:

    Tut es doch nicht. In beiden Fällen initialisierst Du ein Objekt bei der Konstruktion mit zwei Werten. Aus Abstraktionssicht geht es kaum besser.

    Aus technischer Sicht ginge es um einiges besser. Ich bin mir sicher, dass jene Syntax noch einige C++-Programmierer verwirren wird. Man kann Listen innerhalb {} nicht konsequent für Konstruktor-Argumente verwenden, ohne ins Messer zu laufen. Sobald nämlich ein Konstruktor std::initializer_list nimmt, hat man ein anderes Verhalten. In dieser Hinsicht hätte ich es für sinnvoller gehalten, so eine "uniforme" Regel, die eh nur in einem Teil der Fälle einsetzbar ist, gar nicht erst einzuführen.

    krümelkacker schrieb:

    Wieso "plötzlich eine ganz andere Bedeutung"? Diese Art der Initialisierung für nicht-Aggregat-Klassen gab es doch vorher gar nicht. Es zwingt dich ja keiner, nur noch die geschweiften Klammern zu nutzen.

    Ich meine, dass bestehender C++0x-Code, der die geschweiften Klammern für einen "normalen" Konstruktor verwendet, plötzlich fehlerhaft wird (oder noch schlimmer, dennoch kompiliert), wenn in die Klasse ein Konstruktor mit std::initializer_list hinzugefügt wird.

    Was macht man eigentlich bei einem Konstruktor, der als erstes Argument eine std::initializer_list und als zweites ein int nimmt (geht das überhaupt?)? Und wenn die Reihenfolge umgekehrt ist?

    krümelkacker schrieb:

    Ist das nicht intuitiver so? Wenn das nicht viele als intuitiv empfunden hätten, hätte man sich die initializer_list-Konstruktoren für Container-Klassen auch gespart, denke ich.

    Ich finde, man hat es etwas übertrieben, indem man alle möglichen Schreibweisen erlaubt, um es möglichst vielen Recht zu machen. Das neue Sprachmittel std::initializer_list gefällt mir sehr, aber die Umsetzung im Zusammenhang mit der Initialisierung finde ich teilweise sehr fragwürdig. Ideal fände ich so eine Syntax (wobei args eine komma-separierte Liste und expr ein einzelner Ausdruck ist):

    T obj = expr;    // 1. Konvertierungs- oder Kopierkonstruktor
    T obj(args);     // 2. alle Konstruktoren
    T obj = {args};  // 3. impliziter Konstruktor mit std::initializer_list (konsistent mit 1.)
    T obj({args});   // 4. expliziter Konstruktor mit std::initializer_list (konsistent mit 2.)
    

    Man hätte Aufzählungen innerhalb {} einfach als Literal für std::initializer_list interpretieren können, ohne dass es manchmal eine Initialisierungsliste ist, manchmal eine Konstruktor-Argumentliste, und manchmal nur ein einzelner skalarer Wert. Warum folgende Schreibweisen überhaupt möglich sind, sehe ich nicht ein.

    T obj = {expr};  // 5. einzelner Ausdruck, ohne std::initializer_list
    T obj {expr};    // 6. ebenfalls
    T obj {args};    // 7. hier wieder mit std::initializer_list
    

    Das Ganze ist sowas von inkonsistent, ich verstehe nicht wie man das "uniform" nennen kann. Nur weil man die geschweiften Klammern nun für alles einsetzen kann, ist das noch lange nicht einheitlich. Zumal die anderen Schreibweisen ja ungestört weiterexistieren und ein Gemisch vorprogrammiert ist. Ich werde jedenfalls versuchen, möglichst nur die oberen Varianten zu benutzen.



  • Nexus schrieb:

    Tachyon schrieb:

    Tut es doch nicht. In beiden Fällen initialisierst Du ein Objekt bei der Konstruktion mit zwei Werten. Aus Abstraktionssicht geht es kaum besser.

    Aus technischer Sicht ginge es um einiges besser. Ich bin mir sicher, dass jene Syntax noch einige C++-Programmierer verwirren wird. Man kann Listen innerhalb {} nicht konsequent für Konstruktor-Argumente verwenden, ohne ins Messer zu laufen. Sobald nämlich ein Konstruktor std::initializer_list nimmt, hat man ein anderes Verhalten. In dieser Hinsicht hätte ich es für sinnvoller gehalten, so eine "uniforme" Regel, die eh nur in einem Teil der Fälle einsetzbar ist, gar nicht erst einzuführen.

    Gib mal ein Beispiel, wo es schädlich ist. Irgendwie leuchtet mit nicht ein, wo eine Unterscheidung zwischen initializer_list und nicht initializer_list einen Vorteil hätte. Zumal es je Regeln gibt, die potentielle Mehrdeutigkeiten auflösen.


Anmelden zum Antworten