[smart_ptr> C++0x unique_ptr ?

Hello, I was wondering whether there was any plan to implement a unique_ptr in boost, that would replace scoped_ptr, and would default to scoped_ptr on compilers lacking move semantic? Best regards, -- Loïc

Isn't the 'unique_ptr<>' a replacement for the standard C++ auto_ptr<> that's going to be deprecated in the next standard? -Sid Sacek -----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Loïc Joly Sent: Saturday, July 12, 2008 6:10 PM To: boost@lists.boost.org Subject: [boost] [smart_ptr> C++0x unique_ptr ? Hello, I was wondering whether there was any plan to implement a unique_ptr in boost, that would replace scoped_ptr, and would default to scoped_ptr on compilers lacking move semantic? Best regards, -- Loïc _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Sid Sacek a écrit :
Isn't the 'unique_ptr<>' a replacement for the standard C++ auto_ptr<> that's going to be deprecated in the next standard?
It is both a replacement for std::auto_ptr & for boost::scoped_ptr : - It has single ownership, like scoped_ptr - It can transfert its ownership, like auto_ptr (but does so by move semantic, not by a a copy with a strange semantic). - It also has custom deleter semantic -- Loïc

On Jul 12, 2008, at 6:32 PM, Loïc Joly wrote:
Sid Sacek a écrit :
Isn't the 'unique_ptr<>' a replacement for the standard C++ auto_ptr<> that's going to be deprecated in the next standard?
It is both a replacement for std::auto_ptr & for boost::scoped_ptr : - It has single ownership, like scoped_ptr - It can transfert its ownership, like auto_ptr (but does so by move semantic, not by a a copy with a strange semantic). - It also has custom deleter semantic
And scoped_array functionality: unique_ptr<char[]> p(new char[3]); p[0] = 'a'; :-) Example implementation: http://home.twcny.rr.com/hinnant/cpp_extensions/unique_ptr.html -Howard

on Sat Jul 12 2008, Loïc Joly <loic.actarus.joly-AT-numericable.fr> wrote:
Hello,
I was wondering whether there was any plan to implement a unique_ptr in boost, that would replace scoped_ptr, and would default to scoped_ptr on compilers lacking move semantic?
You might look at http://svn.boost.org/trac/boost/browser/trunk/boost/ptr_container/detail/sta... and http://www.coderage.com/move_ptr/ -- Dave Abrahams BoostPro Computing http://www.boostpro.com

David Abrahams wrote:
on Sat Jul 12 2008, Loïc Joly <loic.actarus.joly-AT-numericable.fr> wrote:
Hello,
I was wondering whether there was any plan to implement a unique_ptr in boost, that would replace scoped_ptr, and would default to scoped_ptr on compilers lacking move semantic?
You might look at
http://svn.boost.org/trac/boost/browser/trunk/boost/ptr_container/detail/sta...
Looks interesting! Plan to include into boost?

on Mon Jul 14 2008, Neal Becker <ndbecker2-AT-gmail.com> wrote:
David Abrahams wrote:
on Sat Jul 12 2008, Loïc Joly <loic.actarus.joly-AT-numericable.fr> wrote:
Hello,
I was wondering whether there was any plan to implement a unique_ptr in boost, that would replace scoped_ptr, and would default to scoped_ptr on compilers lacking move semantic?
You might look at
http://svn.boost.org/trac/boost/browser/trunk/boost/ptr_container/detail/sta...
Looks interesting! Plan to include into boost?
The former one is in the Boost sourcebase. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Loïc Joly-2 wrote:
I was wondering whether there was any plan to implement a unique_ptr in boost, that would replace scoped_ptr, and would default to scoped_ptr on compilers lacking move semantic?
While std::unique_ptr and std::shared_ptr together cover a lot of functionality, I don't think boost::scoped_ptr should be changed or removed. In some sense, scoped_ptr provides better static guarantees than unique_ptr. If you see a scoped_ptr declared at function scope you can be sure that it is deallocated when the function exits (unless someone abuses it with get()+reset(0)). A unique_ptr might transfer it ownership elsewhere. So scoped_ptr's type tells us something and there is probably lots of code out there which wants to convey this information. Therefor we shouldn't change it's semantics. Also, scoped_ptr and auto_ptr are more lightweight since they do not have the extra level of indirection that a custom deleter requires. If the smart ptr library had been designed today, unique_ptr could have replaced scoped_ptr, IMHO. The extra type information of scoped_ptr and it's efficiency would probably not be worth the added conceptual overhead of another pointer type. I would find more use in a smart pointers which by type guaranteed that the value is never null. These could introduce a new types like valid_shared_ptr or be policy-based like shared_ptr<T, NullValuePolicy= possibly_null> (shared_ptr<T, non_null> for non-null version). Johan -- View this message in context: http://www.nabble.com/-smart_ptr%3E-C%2B%2B0x-unique_ptr---tp18424143p184595... Sent from the Boost - Dev mailing list archive at Nabble.com.

Johan Torp wrote:
Loïc Joly-2 wrote:
I was wondering whether there was any plan to implement a unique_ptr in boost, that would replace scoped_ptr, and would default to scoped_ptr on compilers lacking move semantic?
While std::unique_ptr and std::shared_ptr together cover a lot of functionality, I don't think boost::scoped_ptr should be changed or removed.
In some sense, scoped_ptr provides better static guarantees than unique_ptr. If you see a scoped_ptr declared at function scope you can be sure that it is deallocated when the function exits (unless someone abuses it with get()+reset(0)). A unique_ptr might transfer it ownership elsewhere.
So scoped_ptr's type tells us something and there is probably lots of code out there which wants to convey this information. Therefor we shouldn't change it's semantics. Also, scoped_ptr and auto_ptr are more lightweight since they do not have the extra level of indirection that a custom deleter requires.
Strongly agree. Many of my uses of scoped_ptr are in industrial code that will be maintained for years or even decades by programmers with very limited familiarity with the code involved. scoped_ptr communicates both to the maintainer and the compiler that ownership is never transfered. That is critical enough to justify a separate type, even though in other respects it is the same as unique_ptr. --Beman

On Jul 15, 2008, at 3:28 AM, Johan Torp wrote:
So scoped_ptr's type tells us something and there is probably lots of code out there which wants to convey this information. Therefor we shouldn't change it's semantics. Also, scoped_ptr and auto_ptr are more lightweight since they do not have the extra level of indirection that a custom deleter requires.
I agree that scoped_ptr provides semantics that unique_ptr doesn't and so should not be removed from boost. However I did want to clarify one bit: sizeof(unique_ptr<int>) == sizeof(auto_ptr<int>) == sizeof(scoped_ptr<int>) == sizeof(int*). And unique_ptr<int> doesn't allocate any extra memory on the heap (unlike shared_ptr, and just like scoped_ptr and auto_ptr). The deleter of unique_ptr is a "static deleter". This is in contrast to shared_ptr's "dynamic deleter". The static deleter requires no overhead unless it contains state (the default_delete is stateless). Simplified: template <class T> class scoped_ptr { T* ptr_; public: explicit scoped_ptr(T* p) : ptr_(p) {} ... }; template <class T, class D = default_delete<T>> class unique_ptr { compressed_pair<T*, D> ptr_; // or a future implementation of tuple<T*, D> ! ;-) public: explicit unique_ptr(T* p) : ptr_(p) {} unique_ptr(T* p, D&& d) : ptr_(p, std::move(d)) {} ... }; template <class T> class shared_ptr { __shared_ptr_base* ptr_; public: template <class Y> explicit shared_ptr(Y* p) : ptr_(new __shared_ptr_imp<Y, default_delete<Y>>(p)) {} template <class Y, class D> shared_ptr(Y* p, D d) : ptr_(new __shared_ptr_imp<Y, D>(p, d)) {} ... }; -Howard

Howard Hinnant wrote:
However I did want to clarify one bit: sizeof(unique_ptr<int>) == sizeof(auto_ptr<int>) == sizeof(scoped_ptr<int>) == sizeof(int*). And unique_ptr<int> doesn't allocate any extra memory on the heap (unlike shared_ptr, and just like scoped_ptr and auto_ptr).
The deleter of unique_ptr is a "static deleter". This is in contrast to shared_ptr's "dynamic deleter". The static deleter requires no overhead unless it contains state (the default_delete is stateless).
I didn't know this. I suppose this means that: 1. All code which potentially destroys a unique_ptr<T> needs to know the complete declaration of T 2. unique_ptr<T> can't be assigned to a unique_ptr<void> Correct? Statement 1 can substantially increase compilation dependencies if everybody who passes around unique_pointers need to know the complete types even though they never use them. This type of code is extremely common. For instance, to expose dependencies you often have one instance of some class and pass it all over the application instead of having a singleton. Raw pointers, references and shared_ptr need not know the full type of the pointer value being passed which removes alot of include statement and thereby compilation dependencies. Can this problem be lessened by passing around r-value references? Statement 2 can be useful when you only care about objects' lifetimes and never expect to use it again. I find this pattern very useful, especially for storing a bunch of boost::signal::scoped_connection and other RAII objects which you just want to tie to some particular objects lifetime. Anyhow, I suppose unique_ptr<T> can be moved to shared_ptr<void> which has dynamic destruction (and can be type erased). Am I right? Johan -- View this message in context: http://www.nabble.com/-smart_ptr%3E-C%2B%2B0x-unique_ptr---tp18424143p184681... Sent from the Boost - Dev mailing list archive at Nabble.com.

On Jul 15, 2008, at 11:30 AM, Johan Torp wrote:
Howard Hinnant wrote:
However I did want to clarify one bit: sizeof(unique_ptr<int>) == sizeof(auto_ptr<int>) == sizeof(scoped_ptr<int>) == sizeof(int*). And unique_ptr<int> doesn't allocate any extra memory on the heap (unlike shared_ptr, and just like scoped_ptr and auto_ptr).
The deleter of unique_ptr is a "static deleter". This is in contrast to shared_ptr's "dynamic deleter". The static deleter requires no overhead unless it contains state (the default_delete is stateless).
I didn't know this. I suppose this means that: 1. All code which potentially destroys a unique_ptr<T> needs to know the complete declaration of T 2. unique_ptr<T> can't be assigned to a unique_ptr<void>
Correct?
Correct on both counts. Also see LWG issue 762 which is in Ready status: http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#762 which clarifies exactly which members of unique_ptr need T to be complete and which don't. (anything that delete's the pointer needs the complete type)
Statement 1 can substantially increase compilation dependencies if everybody who passes around unique_pointers need to know the complete types even though they never use them. This type of code is extremely common. For instance, to expose dependencies you often have one instance of some class and pass it all over the application instead of having a singleton. Raw pointers, references and shared_ptr need not know the full type of the pointer value being passed which removes alot of include statement and thereby compilation dependencies. Can this problem be lessened by passing around r-value references?
I don't believe r-value references would help here. Note though that as long as Base::~Base() is virtual, you can safely pass around a unique_ptr<Base>(new Derived). Also one can wrap a unique_ptr up in a pimpl pattern. As long as the pimpl's destructor is outlined, you can hide the T pretty well. Essentially the same rules which apply to auto_ptr apply to unique_ptr (in this department) with one big exception: If you accidently destruct an incomplete type, unique_ptr will complain at compile time. auto_ptr just silently accepts this error. The design philosophy of unique_ptr is: as much functionality as you can get with no overhead. Let clients who need more functionality obtain it by building their tools on top of unique_ptr (pay as you go) via either wrappers or custom deleters.
Statement 2 can be useful when you only care about objects' lifetimes and never expect to use it again. I find this pattern very useful, especially for storing a bunch of boost::signal::scoped_connection and other RAII objects which you just want to tie to some particular objects lifetime. Anyhow, I suppose unique_ptr<T> can be moved to shared_ptr<void> which has dynamic destruction (and can be type erased). Am I right?
Yes, unique_ptr<T> can be moved to shared_ptr<void>. That may or may not be overkill for a given situation. I tend to prefer the zero- overhead unique_ptr for RAII applications as often as I can get away with it. Having the customizable deleter makes unique_ptr a lot more flexible in the RAII department (compared to scoped/auto_ptr), but not as flexible as shared_ptr. The use of function-pointer-deleters allows for a nifty "poor-man's dynamic deleter". A hybrid if you will between the typical unique_ptr and shared_ptr use cases and overhead: unique_ptr<int, void(*)(void*)> p1(std::malloc(sizeof(int)), std::free); unique_ptr<FILE, int(*)(FILE*)> p2(std::fopen("test.dat", "w"), std::fclose); etc. The function pointer may (for example) point into another dynamic library. One might build a system whereby you "standardize" on unique_ptr<void, void(*)(void*)> but construct the unique_ptr with different function pointers (chosen at run time). Of course since function pointers aren't stateless, you've swelled the sizeof unique_ptr to two words instead of one. But there's still no auxiliary heap access like you would get with shared_ptr. Here's a little of the original motivation: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1856.html#Addition%... The subsection in there titled "Reference Deleter" has a nice RAII demo where the type of the deleter is not known until instantiation time and may be stateful (such as counting how many deallocations it has made). -Howard

Howard Hinnant wrote:
The use of function-pointer-deleters allows for a nifty "poor-man's dynamic deleter". A hybrid if you will between the typical unique_ptr and shared_ptr use cases and overhead:
unique_ptr<int, void(*)(void*)> p1(std::malloc(sizeof(int)), std::free); unique_ptr<FILE, int(*)(FILE*)> p2(std::fopen("test.dat", "w"), std::fclose);
What about boost::function<void*>? How is that less powerful than a dynamic deleter? I personally don't see the point in dynamic deleters, they're only specializations of dynamic ones.

On Jul 16, 2008, at 7:07 AM, Thorsten Ottosen wrote:
Mathias Gaunard skrev:
Mathias Gaunard wrote:
I personally don't see the point in dynamic deleters, they're only specializations of dynamic ones. I meant static ones, sorry.
For the last of first occurrence?
Whos on first? <old joke> Sorry Mathias, if there was a question in there, I don't know what it was. -Howard

Thorsten Ottosen wrote:
Mathias Gaunard skrev:
Mathias Gaunard wrote:
I personally don't see the point in dynamic deleters, they're only specializations of dynamic ones.
I meant static ones, sorry.
For the last of first occurrence?
I meant: "I personally don't see the point in dynamic deleters, they're only specializations of static ones."

On Jul 16, 2008, at 4:51 PM, Mathias Gaunard wrote:
"I personally don't see the point in dynamic deleters, they're only specializations of static ones."
The advantage is that instead of doing this: struct B; class A { public: typedef std::unique_ptr<B, void(*)(B*)> handle; private: handle p_; public: static void reclaim(B*); // used as the deleter A(); // p_ constructed with reclaim handle get() {return std::move(p_);} }; int main() { A a; A::handle p = a.get(); // ... } // call A::reclaim(p.get()) one could do this: struct B; class A { shared_ptr<B> p_; public: static void reclaim(B*); // used as the deleter A(); // p_ constructed with reclaim shared_ptr<B> get() {return p_;} }; int main() { A a; shared_ptr<B> p = a.get(); // ... } // call A::reclaim(p.get()) The difference is that you don't have to use A::handle to mask your smart pointer type. You can advertise that shared_ptr<B> will be your smart pointer type. When you do so, you still leave yourself the freedom to change your destruction policy in the future without disturbing API or ABI. However recall that in the first case you can change your destruction policy by simply rewriting A::reclaim(B*) without disturbing API or ABI as well. The difference here is subtle, and some may even say insignificant. Others will say very significant. If the client of A also has a client C that uses the smart_ptr of A then the closer you can come to a "common well known type" the better. shared_ptr<B> is better known than unique_ptr<B, void(*)(B*)> or A::handle. It is good to have all of these tools in the toolbox: shared_ptr, unique_ptr, scoped_ptr. There are different situations where each one is best. Disclaimer: I currently don't have a good use case for scoped_ptr. But people I highly respect say they do and that's good enough for me. Note: I believe the more important difference between shared_ptr and unique_ptr is the copy vs move characteristic as opposed to the static vs dynamic deleter characteristic. shared_ptr is better suited to handle dynamic deleters because it is already committed to auxiliary heap storage to store the reference count and so can handle the dynamic deleter with nearly zero extra overhead. The same isn't true of unique_ptr. One should generally choose between shared_ptr and unique_ptr based on whether you want copy or shared-ownership semantics vs unique-ownership/move-only semantics. Neither of these choices is universally better than the other (despite the design of some other languages ;-) ). -Howard

Howard Hinnant wrote:
The difference here is subtle, and some may even say insignificant. Others will say very significant. If the client of A also has a client C that uses the smart_ptr of A then the closer you can come to a "common well known type" the better. shared_ptr<B> is better known than unique_ptr<B, void(*)(B*)> or A::handle.
It's only a matter of name. A template typedef is sufficient to get a better name. template<typename T> struct dynamic_unique_ptr : unique_ptr<T, void(*)(T*)> { };

Mathias Gaunard skrev:
Howard Hinnant wrote:
The difference here is subtle, and some may even say insignificant. Others will say very significant. If the client of A also has a client C that uses the smart_ptr of A then the closer you can come to a "common well known type" the better. shared_ptr<B> is better known than unique_ptr<B, void(*)(B*)> or A::handle.
It's only a matter of name. A template typedef is sufficient to get a better name.
template<typename T> struct dynamic_unique_ptr : unique_ptr<T, void(*)(T*)> { };
It is more than a name. The dynamic deleter gives you the possibility to binary stable interfaces and let the implementation vary over time as you like. In the API I wrote a year ago, we used shared_ptr for exactly this purpose. -Thorsten

on Wed Jul 16 2008, Mathias Gaunard <mathias.gaunard-AT-ens-lyon.org> wrote:
Mathias Gaunard wrote:
I personally don't see the point in dynamic deleters, they're only specializations of dynamic ones.
I meant static ones, sorry.
One point is to simplify the type information that ends up in interfaces. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On Jul 16, 2008, at 10:43 AM, David Abrahams wrote:
on Wed Jul 16 2008, Mathias Gaunard <mathias.gaunard-AT-ens- lyon.org> wrote:
Mathias Gaunard wrote:
I personally don't see the point in dynamic deleters, they're only specializations of dynamic ones.
I meant static ones, sorry.
One point is to simplify the type information that ends up in interfaces.
Here's one code snippet that is likely to satisfy stability requirements for both API and ABI that might arise over time: struct B; class A { public: typedef std::unique_ptr<B, void(*)(B*)> handle; private: handle p_; public: static void reclaim(B*); // used as the deleter A(); // p_ constructed with reclaim handle get() {return std::move(p_);} }; int main() { A a; A::handle p = a.get(); // ... } // call A::reclaim(p.get()) B's layout is hidden (can change over time). The details of how to destruct B are hidden (and can change over time). The details of how to create a B are hidden (and can change over time). Destruction of B is automatic. One can pass (move) a handle (if desired) to the resource B and still have it destructed automatically and correctly. If one can suffer ABI changes, but not API changes, the type of the smart pointer is hidden behind a typedef (and can change over time). -Howard
participants (9)
-
Beman Dawes
-
David Abrahams
-
Howard Hinnant
-
Johan Torp
-
Loïc Joly
-
Mathias Gaunard
-
Neal Becker
-
Sid Sacek
-
Thorsten Ottosen