Boost Smart Pointer jetzt Standard C++?
-
Nachgefragt... schrieb:
krümelkacker schrieb:
unique_ptr<int[]> p (new int[123]); unique_ptr<doube> q (new double);
Wie kann man denn im Template solche Unterscheidungen machen?
Also wie kann ich differenzieren, ob ich ein Array reinkriege oder nicht
und mich dementsprechend anders verhalten? (delete vs. delete[])So:
template <class T> struct test { static const int value = 0; }; template <class T> struct test<T []> { static const int value = 1; }; static_assert(test<int>::value == 0); static_assert(test<int[]>::value == 1);
-
Nexus schrieb:
krümelkacker schrieb:
Da Du hier p als öffentlich deklarierst und jemand per std::move den Zeiger irgendwo anders hinverschieben kann oder einfach reset aufrufen kann, brauchst Du hier einen eigenen Deleter, was sonst ggf nicht nötig gewesen wär.
Sorry, das war nur der Einfachheit halber. Was würde ein privates
p
ändern?Damit könntest Du als Klassendesigner garantieren, dass die Elementfunktionen von unique_ptr, die eventuell zu einer Freigabe führen (Zuweisung, reset, Destruktor), nur dort instantiiert werden, wo Incomplete vollständig bekannt ist. Dann brauchst Du keinen eigenen Deleter mehr. Aber kürzer wird es damit nicht, weil Du eben noch Destruktor, Move-Ctor und Move-Zuweisung der MyClass-Klasse innerhalb von myclass.cpp definieren musst:
// header class Incomplete; class MyClass { public: MyClass(); ~MyClass(); MyClass(MyClass&&); MyClass& operator=(MyClass&&) &; private: unique_ptr<Incomplete> ptr_; }; // *.cpp class Incomplete {}; MyClass::MyClass() : ptr_(new Incomplete) {} MyClass::~MyClass() = default; MyClass::MyClass(MyClass&&) = default; MyClass& MyClass::operator=(MyClass&&) = default;
Da das nicht wirklich kürzer ist, würde ich auch bei so etwas einen eigenen Deleter verwenden.
Nexus schrieb:
Du hast Recht, der Aufwand hält sich in Grenzen (wobei du auch alles auf zwei Zeilen gequetscht hast :p).
Naja, so lang sind die ja nicht.
Nexus schrieb:
Doch jedes Mal diesen Boilerplate-Code zu schreiben kann nervig sein. In einem Template auslagern geht auch nicht gut, weil dann wieder der Typ bekannt sein müsste. Man könnte eventuell Type Erasure verwenden...
Ja. Man kann es zB auch mit Funktionszeigern und Lambdas machen. Folgendes sollte funktionieren:
// header class Incomplete; class MyClass { public: MyClass(); private: unique_ptr<Incomplete,void(*)(Incomplete*)> ptr_; }; // *.cpp class Incomplete {}; MyClass::MyClass() : ptr_(new Incomplete,[](Incomplete*p){delete p;}) {}
Es ist noch ein bischen kürzer als die Variante mit dem IDel struct. Das unique_ptr-Objekt wird aber wahrscheinlich etwas größer wegen dem zu speichernden Funktionszeiger. Mit Deletern in Form von Klassen ohne Datenelemente ist es möglich, die vom Standard erlaubte "empty base class optimization" auszunutzen (man kann zB std::tuple<T*,Deleter> als Datenelement-Typ im unique_ptr verwenden).
Nachgefragt... schrieb:
krümelkacker schrieb:
unique_ptr<int[]> p (new int[123]); unique_ptr<doube> q (new double);
Wie kann man denn im Template solche Unterscheidungen machen?
Also wie kann ich differenzieren, ob ich ein Array reinkriege oder nicht
und mich dementsprechend anders verhalten? (delete vs. delete[])Partielle Spezialisierungen. Es gibt eine partielle Spezialisierung von default_deleter für T[], welcher dann delete[] statt delete verwendet und es gibt eine partielle Spezialisierung von unique_ptr für T[], welcher zusätzlich noch den Index-Operator anbietet aber dafür keine Derived->Base-Konvertierung mehr zulässt.
-
Okay, danke für die Erklärung! Scheint so, als würde ich bei unvollständigen Typen besser auf meine Smart-Pointers zurückgreifen. Dafür sind die noch nicht wirklich movable, und für normale RAII-Zeiger lohnt sich
unique_ptr
wohl auch eher.Weiss noch jemand was zur neuen Initialisierung? Also worin genau der Grund bestand, 6. und 7. einzuführen (soweit ich weiss, funktionierte 5. bereits in C++98 für skalare Objekte)? Auch Spekulationen sind willkommen.
-
Nexus schrieb:
Okay, danke für die Erklärung! Scheint so, als würde ich bei unvollständigen Typen besser auf meine Smart-Pointers zurückgreifen.
Mal doof nachgefragt: Wieso scheint das so? So, wie ich das verstanden habe, verwendest Du auch Funktionszeiger für das Löschen. Wo ist da der Unterschied zu
unique_ptr<Incomplete,void(*)(Incomplete*)>
?
-
krümelkacker schrieb:
Mal doof nachgefragt: Wieso scheint das so? So, wie ich das verstanden habe, verwendest Du auch Funktionszeiger für das Löschen. Wo ist da der Unterschied zu
unique_ptr<Incomplete,void(*)(Incomplete*)>
?Ja, aber die Funktionszeiger sind bei mir in der Implementierung und nicht im Client-Code. Vom Mechanismus her ist es das gleiche, aber ich finde meine Smart-Pointer von der Anwendung her einfacher, wenn unvollständige Typen im Spiel sind:
#include "SmartPtr.hpp" class Incomplete; class MyClass { public: MyClass(); private: SmartPtr<Incomplete> p; }; // --- MyClass.cpp ------------------------ #include "MyClass.hpp" class Incomplete {}; MyClass::MyClass() : p(new Incomplete) {}
Also kommt man als Benutzer nie mit Funktionszeigern in Berührung, man muss auch die Grossen Drei nicht überladen. Der Funktionszeiger wird im Konstruktor von
SmartPtr
zugewiesen (wo ja der Typ wegennew
ohnehin bekannt ist).
-
@Nexus:
C++0x Pimpl reloaded:upi.hpp
#ifndef UPI_HPP_INCLUDED #define UPI_HPP_INCLUDED #include <memory> #include <utility> #include "boost/checked_delete.hpp" // upi = unique pointer to incomplete template<class T> using upi = std::unique_ptr<T,void(*)(T*)>; template<class T> void upi_deleter_func(T*ptr) { boost::checked_delete(ptr); } template<class T, class...Args> upi<T> make_upi(Args&&...args) { return { new T(std::forward<Args>(args)...), upi_deleter_func<T> }; } #endif//UPI_HPP_INCLUDED
myclass.hpp
#ifndef MYCLASS_HPP_INCLUDED #define MYCLASS_HPP_INCLUDED #include "upi.hpp" class Incomplete; class MyClass { public: MyClass(); private: upi<Incomplete> ptr_; }; #endif//MYCLASS_HPP_INCLUDED
myclass.cpp
#include "myclass.hpp" class Incomplete {}; MyClass::MyClass() : ptr_(make_upi<Incomplete>()) {}
-
Okay, das wäre eine Idee. Ich hätte jetzt eher an was Konventionelles gedacht (aber ohne eine konkrete Implementierung im Kopf zu haben), ich bin mit den C++0x-Features auch noch zu wenig vertraut.
Aber trotzdem ist es natürlich leicht umständlicher, wenn man nicht das normale
new
verwenden kann. Bis Variadic Templates von Visual Studio unterstützt werden, wirds wohl auch noch eine Weile gehen... Ich werd wohl vorerst bei meinen Smart-Pointern bleiben, auch um C++98-kompatibel zu bleiben (die kommen nämlich in eine Bibliothek). Hoffentlich bist du nicht enttäuschtDennoch vielen Dank für den Vorschlag, so habe ich wieder was gelernt. Auch das
using
scheint wirklich enorm nützlich zu seinEdit: Deinen Edit gelesen
-
Nexus schrieb:
[...] Hoffentlich bist du nicht enttäuscht
Och nee. Ich hab' ja auch was gelernt (bzgl Deiner SmartPtr-Klasse).
-
krümelkacker schrieb:
// header class Incomplete; class MyClass { public: MyClass(); private: unique_ptr<Incomplete,void(*)(Incomplete*)> ptr_; }; // *.cpp class Incomplete {}; MyClass::MyClass() : ptr_(new Incomplete,[](Incomplete*p){delete p;}) {}
Dir ist aber schon klar, dass das nicht standardkonform ist? Eine Lambda-Funktion hat keinen definierten Typ und lässt sich nur beim GCC in einem Funktionszeiger speichern.
-
314159265358979 schrieb:
Dir ist aber schon klar, dass das nicht standardkonform ist? Eine Lambda-Funktion hat keinen definierten Typ und lässt sich nur beim GCC in einem Funktionszeiger speichern.
Siehe n3126.pdf, 5.1.2 [expr.prim.lambda], Absatz 6
The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator.
-
Okay, wieder was dazu gelernt