[GSoC] Extend shared_ptr to support array, update 2

Jonathan Wakely jwakely@redhat.com
Wed Apr 8 10:44:00 GMT 2015


On 04/04/15 20:26 -0400, Fan You wrote:
>Hello,
>
>Things I've tried recently, by adding specialization for
>__shared_ptr<__libfund_v1<T>>
> - [8.2.1.1] shared_ptr constructors.
> - [8.2.1.2] shared_ptr observers.
> - Change element_type in __weak_ptr to remove_extent<_Tp>::type;
> - According to
><http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3641.html>,
>I overload make_shared and allocate_shared with an extra parameter
>(size_t __size) to support allocate memory for array type. However,
>it's not in the standard, am I allow to do this?

Since you're only adding them to namespace std::experimental we can be
a bit more flexible on what is added. I suggest following the proposal
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3939.html
(which has not been accepted into the Library Fundamentals TS).

> - And I am also worried about the support for N-dimension array. The
>deleter seems to be fine, but something like shared_ptr<T[N][]...>,
>shared_ptr<T[][N]...> or shared_ptr<T[][]...> may or may not cause
>extra works. Or should I just not consider these for now?

It might be worth considering them, just to make sure you aren't going
to create problems for yourself later, but I think they should Just
Work.

>@@ -627,6 +629,29 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> 	    }
> 	}
>
>+      template<typename _Tp, typename _Alloc, typename... _Args>
>+	__shared_count(_Sp_make_shared_tag, _Tp*, const _Alloc& __a,
>+		       size_t __size, _Args&&... __args)
>+	: _M_pi(0)
>+	{
>+	  typedef _Sp_counted_ptr_inplace<_Tp, _Alloc, _Lp> _Sp_cp_type;
>+	  typedef typename allocator_traits<_Alloc>::template
>+	    rebind_traits<_Sp_cp_type> _Alloc_traits;
>+	  typename _Alloc_traits::allocator_type __a2(__a);
>+	  _Sp_cp_type* __mem = _Alloc_traits::allocate(__a2, __size);

This allocates space for an array of _Sp_counted_ptr_inplace objects,
which is not the same as a single _Sp_counted_ptr_inplace and an array
of _Tp[__size].

>+	  __try
>+	    {
>+	      _Alloc_traits::construct(__a2, __mem, std::move(__a),
>+		    std::forward<_Args>(__args)...);
>+	      _M_pi = __mem;
>+	    }
>+	  __catch(...)
>+	    {
>+	      _Alloc_traits::deallocate(__a2, __mem, __size);
>+	      __throw_exception_again;
>+	    }
>+	}
>+
> #if _GLIBCXX_USE_DEPRECATED
>       // Special case for auto_ptr<_Tp> to provide the strong guarantee.
>       template<typename _Tp>
>@@ -1175,6 +1200,123 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       __shared_count<_Lp>  _M_refcount;    // Reference counter.
>     };
>
>+  //new array  approach
>+  template <typename _Tp>
>+    struct __libfund_v1 {using type = _Tp;};
>+
>+  //array support revised
>+  template<typename _Tp, _Lock_policy _Lp, unsigned N>
>+    class __shared_ptr<__libfund_v1<_Tp[N]>, _Lp>

My suggestion was to use the __libfund_v1 tag type for all types, not
only array types:

  template<typename _Tp, _Lock_policy _Lp>
    class __shared_ptr<__libfund_v1<_Tp>, _Lp>
    {

Then you make that specialization meet the requirements of the TS,
including using remove_extent, and performing the right SFINAE
constraints on conversions.

That means the existing __shared_ptr<_Tp> is unchanged, and
__shared_ptr<__libfund_v1<_Tp>> meets all the requirements of
std::experimental::shared_ptr for array types and non-array types.

>+    {
>+    public:
>+      using element_type = typename __libfund_v1<_Tp[N]>::type;

This should be using remove_extent here.



>+      template<typename _Tp1>
>+        explicit __shared_ptr(_Tp1* __p)
>+        : _M_ptr(__p), _M_refcount(__p, _M_del) // default deleter

I still don't understand why you have the _M_del member here.

It just wastes space in every object. If the user provides a custom
deleter, it isn't used, and if they don't provide a custom deleter
then _M_refcount will store a copy, so _M_del is never needed!

Just pass a default-constructed object to _M_refcount's constructor:

>+        : _M_ptr(__p), _M_refcount(__p, _D_Deleter{})

Now you don't need the _M_del member.

[...]

I'd prefer this to have a better name, maybe _Array_deleter:

>+      struct _D_Deleter
>+      {
>+        void
>+        operator()(_Tp const *__p)

This member function should be const.


>@@ -1334,12 +1476,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       return __shared_ptr<_Tp, _Lp>();
>     }
>
>-
>   template<typename _Tp, _Lock_policy _Lp>
>     class __weak_ptr
>     {
>     public:
>-      typedef _Tp element_type;
>+
>+      using element_type = typename remove_extent<_Tp>::type;

This changes the behaviour of std::weak_ptr<T[N]> so I think you also
need a partial specialization of __weak_ptr<__libfund_v1<_Tp>>.

>diff --git a/experimental/memory b/experimental/memory
>new file mode 100644
>index 0000000..1c2328f
>--- /dev/null
>+++ b/experimental/memory
>@@ -0,0 +1,120 @@
>+#include <memory>
>+
>+namespace std {
>+namespace experimental {
>+inline namespace fundamentals_v1 {
>+
>+  template<typename _Tp>
>+    class shared_ptr;

It should be possible to use std::experimental::shared_ptr<int> as
well as std::experimental::shared_ptr<int[]>, so you need to define
the primary template, not only the partial specializations for arrays.

>+  //for non default lock policy
>+  template<typename _Tp, _Lock_policy _Lp = __default_lock_policy>
>+    using __shared_ptr = std::__shared_ptr<_Tp, _Lp>;

Nice.

>+  template<typename _Tp, unsigned N>
>+    class shared_ptr<_Tp[N]> : public __shared_ptr<__libfund_v1<_Tp[N]>>

As stated above, std::experimental::shared_ptr<int> should work too,
so I was thinking that you would always use the __libfund_v1 tag type
i.e.

  template<typename _Tp, unsigned N>
    class shared_ptr : public __shared_ptr<__libfund_v1<_Tp>>

This will be used for all types, even when _Tp is not an array type.

>+    {
>+    public:
>+      typedef typename remove_extent_t<_Tp> element_type;

For std::experimental::__shared_ptr<_Tp[]> to work correctly the
remove_extent_t must be used in the base class, or done by the
__libfund_v1 tag type as shown above.

>+
>+      constexpr shared_ptr() noexcept = default;
>+
>+      template<typename _Tp1>
>+        explicit shared_ptr(_Tp1* __p) : __shared_ptr<__libfund_v1<_Tp[N]>>(__p) { }

It probably makes sense to define a typedef to refer to the base type:

      using __base_type = __shared_ptr<__libfund_v1<_Tp>>;

That will simplify all the constructors.


Overall this is looking good, thanks for working on it.



More information about the Libstdc++ mailing list