This paper proposes adding array support to shared_ptr
, via the syntax
shared_ptr<T[]>
and shared_ptr<T[N]>
.
Incorporated wording suggestions from LEWG and LWG.
Incorporated wording suggestions from Daniel Krügler.
Currently, if an apropriate deleter is used, std::shared_ptr
can hold
a pointer to an array allocated with new[]
, but it doesn't offer direct
support for that in the interface. This is an obvious omission, an inconsistency
with std::unique_ptr
, and even the subject of a national body comment
(US 105). This paper suggests we take the steps to rectify this unfortunate situation,
by making shared_ptr<T[]>
and shared_ptr<T[N]>
work as expected.
The proposed changes have been implemented in Boost release 1.53, available from www.boost.org. The Boost distribution contains tests and documentation, which can be browsed online.
boost::make_shared
has also been extended to support arrays. The changes
herein are a necessary prerequisite for the make_shared
extensions,
which are a subject of a separate paper (N3870).
Due to the way shared_ptr
and weak_ptr
are specified and implemented,
they already contain most of the functionality that is required, so the changes are relatively
minor and local in both cases. The underlying semantics are already present, as shared_ptr
can already hold pointers to arrays via an appropriate deleter. In addition, its behavior does not
generally depend on the template parameter T
, which is why most of the code and wording
does not mind dealing with T = U[]
or U[N]
.
It should also be noted that shared_ptr<Y>
is convertible to shared_ptr<T>
when Y*
is convertible to T*
and that it so happens that the core language cooperates
very well with us in this case by making pointers to array types convertible only via qualification conversions,
which is exactly what we want.
The unfortunate occurence of unique_ptr
having lost its support for U[N]
obliges me to
assert that in shared_ptr
case, said support is essentially free, both in terms of implementation
complexity or specification complexity. Please don't remove it. Consider reinstating unique_ptr<U[N]>
instead.
The proposed text suggests the addition of reinterpret_pointer_cast
, to match the other casts and complete
the family. It's useful when dealing with arrays of layout-compatible types, but is not essential for the proposal.
It could be sacrificed if necessary.
(All edits are relative to N3485.)
Add the following to the <memory>
synopsis in 20.6.2 [memory.syn]:
// 20.7.2.2.9, shared_ptr casts: template<class T, class U> shared_ptr<T> static_pointer_cast(shared_ptr<U> const& r) noexcept; template<class T, class U> shared_ptr<T> dynamic_pointer_cast(shared_ptr<U> const& r) noexcept; template<class T, class U> shared_ptr<T> const_pointer_cast(shared_ptr<U> const& r) noexcept; template<class T, class U> shared_ptr<T> reinterpret_pointer_cast(shared_ptr<U> const& r) noexcept;
Make the following change to 20.7.2.2 [util.smartptr.shared]:
public: typedefTtypename remove_extent<T>::type element_type;
Make the following change to 20.7.2.2 [util.smartptr.shared]:
template<class Y> shared_ptr(const shared_ptr<Y>& r,Telement_type *p) noexcept;
Make the following change to 20.7.2.2 [util.smartptr.shared]:
// 20.7.2.2.5, observers:T*element_type* get() const noexcept; T& operator*() const noexcept; T* operator->() const noexcept; element_type& operator[](ptrdiff_t i) const noexcept;
Make the following change to 20.7.2.2 [util.smartptr.shared] p1:
// 20.7.2.2.9, shared_ptr casts: template<class T, class U> shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r) noexcept; template<class T, class U> shared_ptr<T> dynamic_pointer_cast(const shared_ptr<U>& r) noexcept; template<class T, class U> shared_ptr<T> const_pointer_cast(const shared_ptr<U>& r) noexcept; template<class T, class U> shared_ptr<T> reinterpret_pointer_cast(const shared_ptr<U>& r) noexcept;
Change 20.7.2.2 [util.smartptr.shared] p2 as follows:
Specializations ofshared_ptr
shall beCopyConstructible
,CopyAssignable
, andLessThanComparable
, allowing their use in standard containers. Specializations ofshared_ptr
shall be contextually convertible tobool
, allowing their use in boolean expressions and declarations in conditions. The template parameterT
ofshared_ptr
may be an incomplete type.
Add a new paragraph to 20.7.2.2 [util.smartptr.shared] after p4:
For the purposes of subclause 20.7.2 [util.smartptr], a pointer typeY*
is said to be compatible with a pointer typeT*
when eitherY*
is convertible toT*
orY
isU[N]
andT
isU cv []
.
Change 20.7.2.2.1 [util.smartptr.shared.const] p3, p4, p7 as follows:
template<class Y> explicit shared_ptr(Y* p);Requires:p
shall be convertible toT*
.Y
shall be a complete type. The expressiondelete p
shall be well formed, shall have well defined behavior, and shall not throw exceptions.Y
shall be a complete type. The expressiondelete[] p
, whenT
is an array type, ordelete p
, whenT
is not an array type, shall be well-formed, shall have well defined behavior, and shall not throw exceptions. WhenT
isU[N]
,Y(*)[N]
shall be convertible toT*
; whenT
isU[]
,Y(*)[]
shall be convertible toT*
; otherwise,Y*
shall be convertible toT*
.Effects: WhenT
is not an array type, cConstructs ashared_ptr
object that owns the pointerp
. Otherwise, constructs ashared_ptr
that ownsp
and a deleter of an unspecified type that callsdelete[] p
.Posconditions:use_count() == 1 && get() == p
.Throws:bad_alloc
, or an implementation-defined exception when a resource other than memory could not be obtained.Exception safety: If an exception is thrown,delete p
is called whenT
is not an array type,delete[] p
otherwise.
Change 20.7.2.2.1 [util.smartptr.shared.const] p8 as follows:
template<class Y, class D> shared_ptr(Y* p, D d); template<class Y, class D, class A> shared_ptr(Y* p, D d, A a); template <class D> shared_ptr(nullptr_t p, D d); template <class D, class A> shared_ptr(nullptr_t p, D d, A a);Requires:p
shall be convertible toT*
.D
shall beCopyConstructible
. The copy constructor and destructor ofD
shall not throw exceptions. The expressiond(p)
shall be well formed, shall have well defined behavior, and shall not throw exceptions.A
shall be an allocator (17.6.3.5). The copy constructor and destructor ofA
shall not throw exceptions. WhenT
isU[N]
,Y(*)[N]
shall be convertible toT*
; whenT
isU[]
,Y(*)[]
shall be convertible toT*
; otherwise,Y*
shall be convertible toT*
.
Change the line between 20.7.2.2.1 [util.smartptr.shared.const] p12 and p13 as follows:
template<class Y> shared_ptr(const shared_ptr<Y>& r,Telement_type *p) noexcept;
Change 20.7.2.2.1 [util.smartptr.shared.const] p17 as follows:
shared_ptr(const shared_ptr& r) noexcept; template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept;Requires: The second constructor shall not participate in the overload resolution unlessY*
isimplicitly convertible tocompatible withT*
.
Change 20.7.2.2.1 [util.smartptr.shared.const] p20 as follows:
shared_ptr(shared_ptr&& r) noexcept; template<class Y> shared_ptr(shared_ptr<Y>&& r) noexcept;Requires: The second constructor shall not participate in the overload resolution unlessY*
isimplicitly convertible tocompatible withT*
.
Change 20.7.2.2.1 [util.smartptr.shared.const] p23 as follows:
template<class Y> explicit shared_ptr(const weak_ptr<Y>& r);Requires:Y*
shall beconvertible tocompatible withT*
.
Add a new paragraph before 20.7.2.2.1 [util.smartptr.shared.const] p33:
template <class Y, class D> shared_ptr(unique_ptr<Y, D>&&r);Requires:Y*
shall be compatible withT*
.Effects: Equivalent toshared_ptr(r.release(), r.get_deleter())
whenD
is not a reference type, otherwiseshared_ptr(r.release(), ref(r.get_deleter()))
.
Change 20.7.2.2.5 [util.smartptr.shared.obs] p1-p6 and the preceding line as follows:
T*element_type* get() const noexcept;Returns: the stored pointer.
T& operator*() const noexcept;Requires:get() != 0
.Returns:*get()
.Remarks: WhenT
is an array type or cv-qualifiedvoid
, it is unspecified whether this member function is declared. If it is declared, it is unspecified what its return type is, except that the declaration (although not necessarily the definition) of the function shall be well formed.
T* operator->() const noexcept;Requires:get() != 0
.Returns:get()
.Remarks: WhenT
is an array type, it is unspecified whether this member function is declared. If it is declared, it is unspecified what its return type is, except that the declaration (although not necessarily the definition) of the function shall be well formed.
element_type& operator[](ptrdiff_t i) const noexcept;Requires:get() != 0 && i >= 0
. IfT
isU[N]
,i < N
.Returns:get()[i]
.Remarks: WhenT
is not an array type, it is unspecified whether this member function is declared. If it is declared, it is unspecified what its return type is, except that the declaration (although not necessarily the definition) of the function shall be well formed.
Change 20.7.2.2.9 [util.smartptr.shared.cast] p1, p2, p3 as follows:
template<class T, class U> shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r) noexcept;Requires: The expressionstatic_cast<T*>(r.get())
static_cast<T*>((U*)0)
shall be well formed.Returns:Ifr
is empty, an emptyshared_ptr<T>
; otherwise, ashared_ptr<T>
object that storesstatic_cast<T*>(r.get())
and shares ownership withr
shared_ptr<T>(r, static_cast<typename shared_ptr<T>::element_type*>(r.get()))
.Postconditions:w.get() == static_cast<T*>(r.get())
andw.use_count() == r.use_count()
, wherew
is the return value.
Change 20.7.2.2.9 [util.smartptr.shared.cast] p5, p6, p7 as follows:
template<class T, class U> shared_ptr<T> dynamic_pointer_cast(const shared_ptr<U>& r) noexcept;Requires: The expressiondynamic_cast<T*>(r.get())
dynamic_cast<T*>((U*)0)
shall be well formedand shall have well defined behavior.Effects:
- When
dynamic_cast<
returns a nonzero valueT*typename shared_ptr<T>::element_type*>(r.get())p
,ashared_ptr<T>
object that stores a copy of it and shares ownership withr
shared_ptr<T>(r, p)
;- Otherwise,
an emptyshared_ptr<T>
objectshared_ptr<T>()
.Postconditions:w.get() == dynamic_cast<T*>(r.get())
, wherew
is the return value.
Change 20.7.2.2.9 [util.smartptr.shared.cast] p9, p10, p11 as follows:
template<class T, class U> shared_ptr<T> const_pointer_cast(const shared_ptr<U>& r) noexcept;Requires: The expressionconst_cast<T*>(r.get())
const_cast<T*>((U*)0)
shall be well formed.Returns:Ifr
is empty, an emptyshared_ptr<T>
; otherwise, ashared_ptr<T>
object that storesconst_cast<T*>(r.get())
and shares ownership withr
shared_ptr<T>(r, const_cast<typename shared_ptr<T>::element_type*>(r.get()))
.Postconditions:w.get() == const_cast<T*>(r.get())
andw.use_count() == r.use_count()
, wherew
is the return value.
Add the following after 20.7.2.2.9 [util.smartptr.shared.cast] p12:
template<class T, class U> shared_ptr<T> reinterpret_pointer_cast(const shared_ptr<U>& r) noexcept;Requires: The expressionreinterpret_cast<T*>((U*)0)
shall be well formed.Returns:shared_ptr<T>(r, reinterpret_cast<typename shared_ptr<T>::element_type*>(r.get()))
.
Make the following change to 20.7.2.3 [util.smartptr.weak] p1:
public: typedefTtypename remove_extent<T>::type element_type;
Change 20.7.2.3.1 [util.smartptr.weak.const] p3 as follows:
weak_ptr(const weak_ptr& r) noexcept; template<class Y> weak_ptr(const weak_ptr<Y>& r) noexcept; template<class Y> weak_ptr(const shared_ptr<Y>& r) noexcept;Requires: The second and third constructors shall not participate in the overload resolution unlessY*
isimplicitly convertible tocompatible withT*
.
Thanks to Glen Fernandes, whose contribution of boost::make_shared
for arrays prompted the boost::shared_ptr
additions proposed herein.
Thanks to Daniel Krügler for reviewing an earlier revision of this paper and suggesting wording improvements.
Thanks to Jonathan Wakely for providing valuable feedback.
— end