Topic: Inconsistancies between shared_ptr and unique_ptr interfaces
Author: jgottman@carolina.rr.com ("Joe Gottman")
Date: Sun, 4 Sep 2005 20:33:23 GMT Raw View
I just read the
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1856.html, which
among other things proposes a new unique_ptr class to replace auto_ptr. I
noticed several unnecessary inconsistencies between the proposed unique_ptr
class and the existing tr1::shared_ptr that would make it difficult to
convert code from using one to the other, or to write template code that can
take either as a parameter. I am not talking about differences caused by
the differing motivations of the two classes, such as the fact that
shared_ptr's can be copied and unique_ptr's can only be moved. Rather I am
talking about inconsistencies in detail that could easily be changed to make
the two classes interfaces more similar with no run-time cost of time or
space usage.
1) Relational operators: Shared_ptr has only operator ==, operator !=,
and operator <. Unique_ptr has these three plus operator >, operator <=,
and operator>=. This is especially strange because the relational operators
are much more useful for shared_ptr than unique_ptr; for example, two
non-empty unique_ptr's with the default deleter should never compare equal
or equivalent under the !(x < y) && !(y < x) relationship. The three
remaining operators could easily be added for shared_ptr's in the obvious
way.
2) Unique_ptr has partial specializations
template <class X, class D> unique_ptr<X[], D> and
template <class X, class D, size_t N> unique_ptr<X[N], D>
to handle dynamically allocated arrays differently than dynamically
allocated scalars, by replacing operator * and operator-> with operator[],
and by disabling automatic conversions that are not safe for arrays. These
issues also exist for shared_ptr, so the same partial specializations
(without the deleter parameter, of course) should be made for it.
3) Shared_ptr has three special casting functions, const_pointer_cast,
static_pointer_cast, and dynamic_ptr_cast, that convert one type of
shared_ptr to another using the appropriate casts. While these may not be
as necessary for unique_ptr's as they are for shared_ptr's, since their
effects are attainable using unique_ptr's public interface, they would still
be very convenient, especially in template code.
template<class U, class V> unique_ptr<U> dynamic_ptr_cast(unique_ptr<V>
&&p);
template<class U,class D1,class V,class D2> unique_ptr<U, D1>
dynamic_ptr_cast(unique_ptr<V, D2>(&&p);
and similar for const_ptr_cast and static_ptr_cast.
Note these functions would take rvalue references as parameters, to preserve
unique_ptr's movable but not copyable semantics. Dynamic_ptr_cast would be
especially useful, because it could be defined to guarantee that its source
would be unchanged if the dynamic_cast failed.
Joe Gottman
---
[ 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: Mon, 5 Sep 2005 21:42:58 GMT Raw View
In article <0NESe.58979$Tq2.282100@twister.southeast.rr.com>,
jgottman@carolina.rr.com ("Joe Gottman") wrote:
> 1) Relational operators: Shared_ptr has only operator ==, operator !=,
> and operator <. Unique_ptr has these three plus operator >, operator <=,
> and operator>=. This is especially strange because the relational operators
> are much more useful for shared_ptr than unique_ptr; for example, two
> non-empty unique_ptr's with the default deleter should never compare equal
> or equivalent under the !(x < y) && !(y < x) relationship. The three
> remaining operators could easily be added for shared_ptr's in the obvious
> way.
Quite frankly I had never noticed that <=, > and >= were missing from
the shared_ptr spec. From N1431, N1450, and the boost documentation I
can see that this is not an accidental omission. However I am currently
lacking the rationale for the omission.
I agree that there should be consistency between shared_ptr and
unique_ptr on this point one way or the other.
> 2) Unique_ptr has partial specializations
> template <class X, class D> unique_ptr<X[], D> and
> template <class X, class D, size_t N> unique_ptr<X[N], D>
> to handle dynamically allocated arrays differently than dynamically
> allocated scalars, by replacing operator * and operator-> with operator[],
> and by disabling automatic conversions that are not safe for arrays. These
> issues also exist for shared_ptr, so the same partial specializations
> (without the deleter parameter, of course) should be made for it.
You'll get no argument from me on this one! :-)
> 3) Shared_ptr has three special casting functions, const_pointer_cast,
> static_pointer_cast, and dynamic_ptr_cast, that convert one type of
> shared_ptr to another using the appropriate casts. While these may not be
> as necessary for unique_ptr's as they are for shared_ptr's, since their
> effects are attainable using unique_ptr's public interface, they would still
> be very convenient, especially in template code.
> template<class U, class V> unique_ptr<U> dynamic_ptr_cast(unique_ptr<V>
> &&p);
> template<class U,class D1,class V,class D2> unique_ptr<U, D1>
> dynamic_ptr_cast(unique_ptr<V, D2>(&&p);
>
> and similar for const_ptr_cast and static_ptr_cast.
>
> Note these functions would take rvalue references as parameters, to preserve
> unique_ptr's movable but not copyable semantics. Dynamic_ptr_cast would be
> especially useful, because it could be defined to guarantee that its source
> would be unchanged if the dynamic_cast failed.
Another excellent point. A few more details:
Looking just at dynamic_pointer_case (note spelling) as an example for
the other two casts, I believe we need the following _four_ signatures:
template <class U, class D1, class V, class D2>
unique_ptr<U, D1>
dynamic_pointer_cast(unique_ptr<V, D2>&& p);
template <class U, class D1, class V, class D2>
unique_ptr<U, D1>
dynamic_pointer_cast(const unique_ptr<V, D2>&);
template <class U, class V, class D2>
unique_ptr<U>
dynamic_pointer_cast(unique_ptr<V, D2>&& p);
template <class U, class V, class D2>
unique_ptr<U>
dynamic_pointer_cast(const unique_ptr<V, D2>&);
The versions taking a const unique_ptr& should fail to compile but only
if instantiated (this can be implemented via a suitable
template-dependent static_cast).
The first signature should fail to compile if any of the following is
true:
* U is an array type.
* V is an array type.
* D2 is not convertible to D1.
The third signature should fail to compile by the same rules as the
first, but substituting default_delete<U> for D1.
The first signature should not require D1 to be default constructible
(e.g. to handle reference deleter types).
Thanks Joe!
-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 ]