Patchwork [v3] Implement pointer_traits and allocator_traits

login
register
mail settings
Submitter Jonathan Wakely
Date May 28, 2011, 2:53 p.m.
Message ID <BANLkTimyGPKXcCG_43kxBqTLTkYqQFbjNQ@mail.gmail.com>
Download mbox | patch
Permalink /patch/97800/
State New
Headers show

Comments

Jonathan Wakely - May 28, 2011, 2:53 p.m.
Here's a patch to make shared_ptr use allocator_traits, I don't plan
to commit this (at least not yet) but it shows what's needed to use
allocator_traits instead of the C++03 way of doing things.  It uses
allocator_traits<T>::__rebind<U>::__type which is a temporary
substitute for the unsupported allocator_traits<T>::rebind<U> template
alias.

This patch isn't quite backward compatible, because I changed
_Sp_counted_deleter::_My_deleter to derive from the supplied _Alloc
type instead of the rebound type.  I think that's more correct, but
changes the type (and possibly size) of the _M_del member.  The safe
way to make that change is probably to rename _Sp_counted_deleter to
_Sp_counted_deleter2, so that code built against the old header uses
the old type and code built with the new header uses a new, distinct
type.

I completely rewrote _Sp_counted_ptr_inplace because it doesn't need a
deleter, so doesn't need to derive from _Sp_counted_deleter and can
benefit from the EBO. That could be renamed _Sp_counted_ptr_inplace2
for compatibility.

Apart from demonstrating how to make use of allocator_traits to
support both C++03 and C++11 allocators, this patch allows the
following example to work, using allocate_shared to create a
shared_ptr to a type with no public constructors or destructor, by
using a custom allocator which is declared as a friend:

#include <memory>
#include <new>

template<typename T>
struct MyAlloc;

class Private
{
  Private() = default;
  Private(const Private&) = default;
  ~Private() = default;

  friend class MyAlloc<Private>;

public:
  int get() const { return 0; }
};

template<typename T>
struct MyAlloc : std::allocator<Private>
{
  void construct(void* p) { ::new(p) Private(); }
};

int main()
{
  MyAlloc<Private> a;
  auto p = std::allocate_shared<Private>(a);
  return p->get();
}

This allows you to write types which can only be managed
Jonathan Wakely - May 28, 2011, 2:54 p.m.
On 28 May 2011 15:53, Jonathan Wakely wrote:
>
> This allows you to write types which can only be managed

... by shared_ptr and can't be created/copied/destroyed any other way.

Patch

Index: include/bits/shared_ptr_base.h
===================================================================
--- include/bits/shared_ptr_base.h	(revision 174358)
+++ include/bits/shared_ptr_base.h	(working copy)
@@ -323,18 +323,15 @@ 
   template<typename _Ptr, typename _Deleter, typename _Alloc, _Lock_policy _Lp>
     class _Sp_counted_deleter : public _Sp_counted_base<_Lp>
     {
-      typedef typename _Alloc::template
-	  rebind<_Sp_counted_deleter>::other _My_alloc_type;
-
       // Helper class that stores the Deleter and also acts as an allocator.
       // Used to dispose of the owned pointer and the internal refcount
       // Requires that copies of _Alloc can free each other's memory.
       struct _My_Deleter
-      : public _My_alloc_type    // copy constructor must not throw
+      : public _Alloc           // copy constructor must not throw
       {
-	_Deleter _M_del;         // copy constructor must not throw
+	_Deleter _M_del;        // copy constructor must not throw
 	_My_Deleter(_Deleter __d, const _Alloc& __a)
-	: _My_alloc_type(__a), _M_del(__d) { }
+	: _Alloc(__a), _M_del(__d) { }
       };
 
     public:
@@ -353,9 +350,11 @@ 
       virtual void
       _M_destroy() noexcept
       {
-	_My_alloc_type __a(_M_del);
-	this->~_Sp_counted_deleter();
-	__a.deallocate(this, 1);
+	typedef typename allocator_traits<_Alloc>::template
+	  __rebind_traits<_Sp_counted_deleter>::__type _My_alloc_traits;
+	typename _My_alloc_traits::allocator_type __a(_M_del);
+	_My_alloc_traits::destroy(__a, this);
+	_My_alloc_traits::deallocate(__a, this, 1);
       }
 
       virtual void*
@@ -375,51 +374,44 @@ 
 
   // helpers for make_shared / allocate_shared
 
-  template<typename _Tp>
-    struct _Sp_destroy_inplace
-    {
-      void operator()(_Tp* __p) const { if (__p) __p->~_Tp(); }
-    };
-
   struct _Sp_make_shared_tag { };
 
   template<typename _Tp, typename _Alloc, _Lock_policy _Lp>
-    class _Sp_counted_ptr_inplace
-    : public _Sp_counted_deleter<_Tp*, _Sp_destroy_inplace<_Tp>, _Alloc, _Lp>
+    class _Sp_counted_ptr_inplace : public _Sp_counted_base<_Lp>
     {
-      typedef _Sp_counted_deleter<_Tp*, _Sp_destroy_inplace<_Tp>, _Alloc, _Lp>
-	_Base_type;
+      // Helper class that stores the pointer and also acts as an allocator.
+      // Used to dispose of the owned pointer and the internal refcount
+      // Requires that copies of _Alloc can free each other's memory.
+      struct _Impl
+      : public _Alloc           // copy constructor must not throw
+      {
+	_Impl(_Alloc __a) : _Alloc(__a), _M_ptr() { }
+	_Tp* _M_ptr;
+      };
 
     public:
-      explicit
-      _Sp_counted_ptr_inplace(_Alloc __a)
-      : _Base_type(static_cast<_Tp*>(0), _Sp_destroy_inplace<_Tp>(), __a)
-      , _M_storage()
-      {
-	void* __p = &_M_storage;
-	::new (__p) _Tp();  // might throw
-	_Base_type::_M_ptr = static_cast<_Tp*>(__p);
-      }
-
       template<typename... _Args>
 	_Sp_counted_ptr_inplace(_Alloc __a, _Args&&... __args)
-	: _Base_type(static_cast<_Tp*>(0), _Sp_destroy_inplace<_Tp>(), __a)
-	, _M_storage()
+	: _M_impl(__a), _M_storage()
 	{
-	  void* __p = &_M_storage;
-	  ::new (__p) _Tp(std::forward<_Args>(__args)...);  // might throw
-	  _Base_type::_M_ptr = static_cast<_Tp*>(__p);
+	  _M_impl._M_ptr = static_cast<_Tp*>(static_cast<void*>(&_M_storage));
+	  allocator_traits<_Alloc>::construct(__a, _M_impl._M_ptr,
+	      std::forward<_Args>(__args)...); // might throw
 	}
 
+      virtual void
+      _M_dispose() noexcept
+      { allocator_traits<_Alloc>::destroy(_M_impl, _M_impl._M_ptr); }
+
       // Override because the allocator needs to know the dynamic type
       virtual void
       _M_destroy() noexcept
       {
-	typedef typename _Alloc::template
-	    rebind<_Sp_counted_ptr_inplace>::other _My_alloc_type;
-	_My_alloc_type __a(_Base_type::_M_del);
-	this->~_Sp_counted_ptr_inplace();
-	__a.deallocate(this, 1);
+	typedef typename allocator_traits<_Alloc>::template
+	  __rebind_traits<_Sp_counted_ptr_inplace>::__type _My_alloc_traits;
+	typename _My_alloc_traits::allocator_type __a(_M_impl);
+	_My_alloc_traits::destroy(__a, this);
+	_My_alloc_traits::deallocate(__a, this, 1);
       }
 
       // Sneaky trick so __shared_ptr can get the managed pointer
@@ -429,13 +421,14 @@ 
 #ifdef __GXX_RTTI
 	return __ti == typeid(_Sp_make_shared_tag)
 	       ? static_cast<void*>(&_M_storage)
-	       : _Base_type::_M_get_deleter(__ti);
+	       : 0;
 #else
         return 0;
 #endif
       }
 
     private:
+      _Impl _M_impl;
       typename aligned_storage<sizeof(_Tp), alignment_of<_Tp>::value>::type
 	_M_storage;
     };
@@ -468,18 +461,21 @@ 
 	  // The allocator's value_type doesn't matter, will rebind it anyway.
 	  typedef std::allocator<int> _Alloc;
 	  typedef _Sp_counted_deleter<_Ptr, _Deleter, _Alloc, _Lp> _Sp_cd_type;
-	  typedef std::allocator<_Sp_cd_type> _Alloc2;
-	  _Alloc2 __a2;
+	  typedef typename allocator_traits<_Alloc> ::template
+	    __rebind_traits<_Sp_cd_type>::__type _Alloc_traits;
+	  typename _Alloc_traits::allocator_type __a;
+	  _Sp_cd_type* __mem = 0;
 	  __try
 	    {
-	      _M_pi = __a2.allocate(1);
-	      ::new(static_cast<void*>(_M_pi)) _Sp_cd_type(__p, __d);
+	      __mem = _Alloc_traits::allocate(__a, 1);
+	      _Alloc_traits::construct(__a, __mem, __p, std::move(__d));
+	      _M_pi = __mem;
 	    }
 	  __catch(...)
 	    {
 	      __d(__p); // Call _Deleter on __p.
-	      if (_M_pi)
-		__a2.deallocate(static_cast<_Sp_cd_type*>(_M_pi), 1);
+	      if (__mem)
+	        _Alloc_traits::deallocate(__a, __mem, 1);
 	      __throw_exception_again;
 	    }
 	}
@@ -488,18 +484,22 @@ 
 	__shared_count(_Ptr __p, _Deleter __d, _Alloc __a) : _M_pi(0)
 	{
 	  typedef _Sp_counted_deleter<_Ptr, _Deleter, _Alloc, _Lp> _Sp_cd_type;
-	  typedef typename _Alloc::template rebind<_Sp_cd_type>::other _Alloc2;
-	  _Alloc2 __a2(__a);
+	  typedef typename allocator_traits<_Alloc> ::template
+	    __rebind_traits<_Sp_cd_type>::__type _Alloc_traits;
+	  typename _Alloc_traits::allocator_type __a2(__a);
+	  _Sp_cd_type* __mem = 0;
 	  __try
 	    {
-	      _M_pi = __a2.allocate(1);
-	      ::new(static_cast<void*>(_M_pi)) _Sp_cd_type(__p, __d, __a);
+	      __mem = _Alloc_traits::allocate(__a2, 1);
+	      _Alloc_traits::construct(__a2, __mem,
+		  __p, std::move(__d), std::move(__a));
+	      _M_pi = __mem;
 	    }
 	  __catch(...)
 	    {
 	      __d(__p); // Call _Deleter on __p.
-	      if (_M_pi)
-		__a2.deallocate(static_cast<_Sp_cd_type*>(_M_pi), 1);
+	      if (__mem)
+	        _Alloc_traits::deallocate(__a2, __mem, 1);
 	      __throw_exception_again;
 	    }
 	}
@@ -510,18 +510,19 @@ 
 	: _M_pi(0)
 	{
 	  typedef _Sp_counted_ptr_inplace<_Tp, _Alloc, _Lp> _Sp_cp_type;
-	  typedef typename _Alloc::template rebind<_Sp_cp_type>::other _Alloc2;
-	  _Alloc2 __a2(__a);
+	  typedef typename allocator_traits<_Alloc> ::template
+	    __rebind_traits<_Sp_cp_type>::__type _Alloc_traits;
+	  typename _Alloc_traits::allocator_type __a2(__a);
+	  _Sp_cp_type* __mem = _Alloc_traits::allocate(__a2, 1);
 	  __try
 	    {
-	      _M_pi = __a2.allocate(1);
-	      ::new(static_cast<void*>(_M_pi)) _Sp_cp_type(__a,
+	      _Alloc_traits::construct(__a2, __mem, std::move(__a),
 		    std::forward<_Args>(__args)...);
+	      _M_pi = __mem;
 	    }
 	  __catch(...)
 	    {
-	      if (_M_pi)
-		__a2.deallocate(static_cast<_Sp_cp_type*>(_M_pi), 1);
+	      _Alloc_traits::deallocate(__a2, __mem, 1);
 	      __throw_exception_again;
 	    }
 	}