Topic: unique_ptr
Author: cppljevans@cox-internet.com (Larry Evans)
Date: Wed, 21 Sep 2005 18:22:52 GMT Raw View
On 09/05/2005 05:44 PM, thorsten.ottosen@dezide.com wrote:
[snip]
> While we're discussion potential correctness issues, then
> we should probably consider adding functions like
>
> template<class T, class U>
> unique_ptr<T> make_unique_ptr( U&& );
>
> [etc for X arguments].
>
> The motivating example would be
>
> foo( make_unique_ptr<T>( "foo" ), make_unique_ptr<T>( "bar" ) );
>
> which is exception-safe whereas
>
> foo( unique_ptr<T>( new T("foo") ), unique_ptr<T>( new T("bar") ) );
>
> is not.
>
> Similar stuff has been discussed for shared_ptr.
>
Is this similar stuff referring to the part of:
http://lists.boost.org/Archives/boost/2003/01/43503.php
about auto_ptr_new<T>? IOW, is David's auto_ptr_new your
make_unique_ptr?
TIA.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "l.j.persson@gmail.com" <l.j.persson@gmail.com>
Date: Thu, 1 Sep 2005 23:11:56 CST Raw View
Hello,
I was just reading the excellent proposal on unique_ptr
(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1856.html).
But I have a question on the constructor: why doesn't it take a T*&&
parameter?
explicit unique_ptr(T*&& p);
If I understand rvalues correctly that would mean:
unique_ptr<int> ui(new int); // Ok
int* pi = new int;
unique_ptr<int> ui(pi); // Not Ok
In current implementation we have
int* pi = new int;
unique_ptr<int> ui1(pi); // Ok
unique_ptr<int> ui2(pi); // Ok, but no good
same applies to operator=() and reset().
/ Jonas
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Sat, 3 Sep 2005 05:23:04 GMT Raw View
In article <1125603471.570361.32720@o13g2000cwo.googlegroups.com>,
"l.j.persson@gmail.com" <l.j.persson@gmail.com> wrote:
> Hello,
> I was just reading the excellent proposal on unique_ptr
> (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1856.html).
> But I have a question on the constructor: why doesn't it take a T*&&
> parameter?
>
> explicit unique_ptr(T*&& p);
>
> If I understand rvalues correctly that would mean:
>
> unique_ptr<int> ui(new int); // Ok
> int* pi = new int;
> unique_ptr<int> ui(pi); // Not Ok
>
> In current implementation we have
>
> int* pi = new int;
> unique_ptr<int> ui1(pi); // Ok
> unique_ptr<int> ui2(pi); // Ok, but no good
>
> same applies to operator=() and reset().
That's a very interesting suggestion!
I must admit that I'm waffling on it at the moment. At first I thought
it sounded great, and I prototyped it right up. But then I used that
prototype to start looking at some of my use cases, and my enthusiasm is
waning.
There are times when you really do want to assign an lvalue pointer to a
unique_ptr. In fact N1856 outlines one such case:
// A::foo establishes ownership of p, but
// must acquire other resources to do so. A local
// unique_ptr is used as an aid to hold on to p while
// those other resources are acquired.
template<class T, class D>
void
A<T, D>::foo(T* p)
{
// Establish preliminary ownership without requiring
// a copy of the deleter D
std::unique_ptr<T, D&> hold(p, deleter_); // no throw
// acquire resources // if throws,
// ... // deleter_(p) executed
// transfer ownership to A
p_ = hold.release(); // no throw
}
With your suggestion that would change to:
template<class T, class D>
void
A<T, D>::foo(T* p)
{
// Establish preliminary ownership without requiring
// a copy of the deleter D
std::unique_ptr<T, D&> hold(std::move(p), deleter_); // no throw
// acquire resources // if throws,
// ... // deleter_(p) executed
// transfer ownership to A
p_ = hold.release(); // no throw
}
Now that doesn't look too bad. But then I went back to the source, the
actual code that the A::foo example is derived from, and it begins to
look worse:
template<class T>
template<class Y, class D>
shared_ptr<T>::shared_ptr(Y* p, D&& d)
: ptr_(p)
{
Metrowerks::move_ptr<Y, D&> hold(p, d);
s_ = new detail::shared_ptr_deleter<Y, D>(p, d);
hold.release();
...
}
Now you can see that "p" is assigned several places in this code. Which
of those places should be changed to move(p)?
(Metrowerks::move_ptr is the proposed std::unique_ptr).
The fact is that after I "own" p with the unique_ptr, I subsequently
also pass ownership of p to the shared_ptr - without first relinquishing
ownership from the unique_ptr! And that is as it should be. So should
the code look like this:
template<class T>
template<class Y, class D>
shared_ptr<T>::shared_ptr(Y* p, D&& d)
: ptr_(p)
{
Metrowerks::move_ptr<Y, D&> hold(move(p), d);
s_ = new detail::shared_ptr_deleter<Y, D>(move(p), d);
hold.release();
...
}
I guess what I'm getting at, and not expressing too eloquently at the
moment, is that I believe accepting only rvalue pointers in the
unique_ptr ctor might lead to code that just liberally sprinkled move(p)
expressions around, and the resulting code is really no safer than it is
without the move(p) expressions.
Ok, gathering my thoughts a little more (maybe):
Assertion:
An rvalue pointer is not intrinsically safer to pass to a smart pointer
than is an lvalue pointer.
But this is a most interesting suggestion! And thank you for making it.
I would be most interested to continue this discussion and collect a
wide range of opinions. This is a feature I had not previously thought
about and we would be remiss not to fully explore it.
-Howard
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Jonas Persson" <l.j.persson@gmail.com>
Date: Sat, 3 Sep 2005 20:20:16 CST Raw View
I dont think it would sprinkle the code with move.
The only(?) places where you assign it lvaues is from a local variable:
int* i = new int:
unique_ptr<int> ip(move(i));
which is never necessary and not to be recomended, and from prameters:
void foo(int *i) {
unique_ptr<int> ip(move(i));
}
in which case the function resumes ownership of a pointer parameter.
That should never happen except for smart pointer ctors. The correct
signature of such a function should be: void foo(unique_ptr<int> i).
The major reason for unsafe code is memory owned by a naked pointer.
Making passing lvalues less convenient than rvalues makes using naked
pointers less convenient.
In your example, it is not nesessary to use move after the first one
anyway. Probably get() is a better choise:
template<class T>
template<class Y, class D>
shared_ptr<T>::shared_ptr(Y* p, D&& d)
: ptr_(p)
{
Metrowerks::move_ptr<Y, D&> hold(move(p), d);
s_ = new detail::shared_ptr_deleter<Y, D>(hold.get(), d);
hold.release();
...
}
that way it is clear for the reader that you are giving the deleter a
pointer already owned by hold. p should not be used after the first
line regardless of move or no move.
/ Jonas
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Sun, 4 Sep 2005 20:33:50 GMT Raw View
In article <1125782448.174120.58820@g14g2000cwa.googlegroups.com>,
"Jonas Persson" <l.j.persson@gmail.com> wrote:
> I dont think it would sprinkle the code with move.
> The only(?) places where you assign it lvaues is from a local variable:
>
> int* i = new int:
> unique_ptr<int> ip(move(i));
>
> which is never necessary and not to be recomended, and from prameters:
>
> void foo(int *i) {
> unique_ptr<int> ip(move(i));
> }
>
> in which case the function resumes ownership of a pointer parameter.
> That should never happen except for smart pointer ctors.
<nit> There are non-smart-pointer classes that may take ownership of
pointers (e.g. strstream). But even so, your point is well taken.
> The correct
> signature of such a function should be: void foo(unique_ptr<int> i).
>
> The major reason for unsafe code is memory owned by a naked pointer.
> Making passing lvalues less convenient than rvalues makes using naked
> pointers less convenient.
>
> In your example, it is not nesessary to use move after the first one
> anyway. Probably get() is a better choise:
>
> template<class T>
> template<class Y, class D>
> shared_ptr<T>::shared_ptr(Y* p, D&& d)
> : ptr_(p)
> {
> Metrowerks::move_ptr<Y, D&> hold(move(p), d);
> s_ = new detail::shared_ptr_deleter<Y, D>(hold.get(), d);
> hold.release();
> ...
> }
>
> that way it is clear for the reader that you are giving the deleter a
> pointer already owned by hold. p should not be used after the first
> line regardless of move or no move.
You make some good points. While reading your post, and re-reading your
first post, a few more things have occurred to me:
1. What if the unique_ptr<T>(T* ...) constructors looked as you suggest:
> explicit unique_ptr(T*&& p);
AND, said constructors zero'd p? I.e.:
void foo(T* p)
{
std::unique_ptr<T> up(std::move(p));
assert(p == 0);
}
This would actually put teeth into what it means to move from a built-in
pointer type. The ownership of the memory really is transferred away
from the built-in pointer.
2. If thought 1 (zeroing the input pointer) is reasonable, does it
still make sense to restrict the input parameter to an rvalue. The
constructors are already marked explicit, so the transfer of memory
ownership is never implicit. Do we need both the words "unique_ptr" and
"move" in the statement to call this fact out, or is "unique_ptr" itself
enough?
void foo(T* p)
{
std::unique_ptr<T> up(p); // sufficiently explicit?
assert(p == 0);
std::unique_ptr<T> up2(p); // up2 is null
}
3. This issue isn't specific to unique_ptr. For any smart pointer for
which the following would be bad:
T* p = ...
smart_pointer<T> p1(p);
smart_pointer<T> p2(p);
this same issues exists. This includes shared_ptr and most any other
smart pointer I can come up with. This gives us vastly more experience
to guide us. For example it is an interesting exercise to go through
the excellent "shared_ptr techniques" page to see how these proposed
changes might effect these examples:
http://www.boost.org/libs/smart_ptr/sp_techniques.html
Would we be willing to have all standard smart pointers behave in the
same way with respect to how they accept raw pointers (be it rvalue
only, zeroing, whatever)?
-Howard
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Jonas Persson" <l.j.persson@gmail.com>
Date: Mon, 5 Sep 2005 16:44:44 CST Raw View
> 1. What if the unique_ptr<T>(T* ...) constructors looked as you suggest:
>
> > explicit unique_ptr(T*&& p);
>
> AND, said constructors zero'd p? I.e.:
>
> void foo(T* p)
> {
> std::unique_ptr<T> up(std::move(p));
> assert(p == 0);
>
> }
>
> This would actually put teeth into what it means to move from a built-in
> pointer type. The ownership of the memory really is transferred away
> from the built-in pointer.
>
> 2. If thought 1 (zeroing the input pointer) is reasonable, does it
> still make sense to restrict the input parameter to an rvalue. The
> constructors are already marked explicit, so the transfer of memory
> ownership is never implicit. Do we need both the words "unique_ptr" and
> "move" in the statement to call this fact out, or is "unique_ptr" itself
> enough?
>
> void foo(T* p)
> {
> std::unique_ptr<T> up(p); // sufficiently explicit?
> assert(p == 0);
> std::unique_ptr<T> up2(p); // up2 is null
>
> }
>
That would be a nice feature, at least in debug builds. Null
dereferences is easier to track down than double deletes..
But it is a runtime check for something that could be checked at
compile time, so I see it as a complement rather than a replacement.
I would expect a high quality compiler or lint tool to emit a warning
if I reused a std::moved lvalue.
Probably they could identify unique_ptr and some other smart pointers
too, but the less special cases the better.
Also, there is this issue with junior programmers. They need simple
rules.
"Do not use a variable after it has been used in std::move" is such a
nice simple rule.
> 3. This issue isn't specific to unique_ptr. For any smart pointer for
> which the following would be bad:
>
> T* p = ...
> smart_pointer<T> p1(p);
> smart_pointer<T> p2(p);
>
> this same issues exists. This includes shared_ptr and most any other
> smart pointer I can come up with. This gives us vastly more experience
> to guide us. For example it is an interesting exercise to go through
> the excellent "shared_ptr techniques" page to see how these proposed
> changes might effect these examples:
>
> http://www.boost.org/libs/smart_ptr/sp_techniques.html
>
I've skimmed through this, and there are som small changes but nothing
that would prevent any of the techniques. I think that making this
trickery a little more verbose is a small price to pay for safety in
the more common cases.
> Would we be willing to have all standard smart pointers behave in the
> same way with respect to how they accept raw pointers (be it rvalue
> only, zeroing, whatever)?
>
Yes, I think this is how they should all work.
/ Jonas
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: thorsten.ottosen@dezide.com
Date: Mon, 5 Sep 2005 16:44:19 CST Raw View
Howard Hinnant wrote:
> In article <1125603471.570361.32720@o13g2000cwo.googlegroups.com>,
> "l.j.persson@gmail.com" <l.j.persson@gmail.com> wrote:
>
> > Hello,
> > I was just reading the excellent proposal on unique_ptr
> > (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1856.html).
> > But I have a question on the constructor: why doesn't it take a T*&&
> > parameter?
> >
> > explicit unique_ptr(T*&& p);
> >
> > If I understand rvalues correctly that would mean:
> >
> > unique_ptr<int> ui(new int); // Ok
> > int* pi = new int;
> > unique_ptr<int> ui(pi); // Not Ok
[snip]
> That's a very interesting suggestion!
>
> I must admit that I'm waffling on it at the moment. At first I thought
> it sounded great, and I prototyped it right up. But then I used that
> prototype to start looking at some of my use cases, and my enthusiasm is
> waning.
While we're discussion potential correctness issues, then
we should probably consider adding functions like
template<class T, class U>
unique_ptr<T> make_unique_ptr( U&& );
[etc for X arguments].
The motivating example would be
foo( make_unique_ptr<T>( "foo" ), make_unique_ptr<T>( "bar" ) );
which is exception-safe whereas
foo( unique_ptr<T>( new T("foo") ), unique_ptr<T>( new T("bar") ) );
is not.
Similar stuff has been discussed for shared_ptr.
br
-Thorsten
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Vladimir Marko" <swelef@post.sk>
Date: Mon, 5 Sep 2005 16:42:29 CST Raw View
Howard Hinnant wrote:
[...]
> 1. What if the unique_ptr<T>(T* ...) constructors looked as you suggest:
>
> > explicit unique_ptr(T*&& p);
>
> AND, said constructors zero'd p? I.e.:
>
> void foo(T* p)
> {
> std::unique_ptr<T> up(std::move(p));
> assert(p == 0);
> }
>
> This would actually put teeth into what it means to move from a built-in
> pointer type. The ownership of the memory really is transferred away
> from the built-in pointer.
It would be great in debug mode. In release mode I would like the
compiler to optimize away the zeroing statement. ("You don't pay for
what you don't need.")
[...]
> 3. This issue isn't specific to unique_ptr. For any smart pointer for
> which the following would be bad:
>
> T* p = ...
> smart_pointer<T> p1(p);
> smart_pointer<T> p2(p);
Well, this is "almost good" for intrusive_ptr -- it should work, but it
provides a bad example for those who don't undestand what's going on
under the hood.
[...]
> Would we be willing to have all standard smart pointers behave in the
> same way with respect to how they accept raw pointers (be it rvalue
> only, zeroing, whatever)?
That would be good for new smart pointers. But for the old ones I'd go
for the backward compatibility.
Vladimir Marko
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]