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                       ]