This paper proposes adding array support to shared_ptr, via the syntax
shared_ptr<T[]> and shared_ptr<T[N]>.
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 (N3641).
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*element_type* 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_ptrshall beCopyConstructible,CopyAssignable, andLessThanComparable, allowing their use in standard containers. Specializations ofshared_ptrshall be contextually convertible tobool, allowing their use in boolean expressions and declarations in conditions. The template parameterTofshared_ptrmay be an incomplete type.
Add a new paragraph to 20.7.2.2 [util.smartptr.shared] after p4:
For the purposes of this section, a typeY*is said to be compatible with a typeT*when eitherY*is convertible toT*orYisU[N]andTisU cv [], where cv is some combination of cv-qualifiers.
Change 20.7.2.2.1 [util.smartptr.shared.const] p3 as follows:
Requires:pshall be convertible toT*.Yshall be a complete type. The expressiondelete pshall be well formed, shall have well defined behavior, and shall not throw exceptions.Yshall be a complete type. The expressiondelete[] p, whenTis an array type, ordelete p, whenTis not an array type, shall be well-formed, shall have well defined behavior, and shall not throw exceptions. WhenTisU[N],Y(*)[N]shall be convertible toT*; whenTisU[],Y(*)[]shall be convertible toT*; otherwise,Y*shall be convertible toT*.
Change 20.7.2.2.1 [util.smartptr.shared.const] p4 as follows:
Effects: WhenTis not an array type, cConstructs ashared_ptrobject that owns the pointerp. Otherwise, constructs ashared_ptrthat ownspand a deleter of an unspecified type that callsdelete[] p.
Change 20.7.2.2.1 [util.smartptr.shared.const] p7 as follows:
Exception safety: If an exception is thrown,delete pis called whenTis not an array type,delete[] potherwise.
Change 20.7.2.2.1 [util.smartptr.shared.const] p8 as follows:
Requires:pshall be convertible toT*.Dshall beCopyConstructible. The copy constructor and destructor ofDshall not throw exceptions. The expressiond(p)shall be well formed, shall have well defined behavior, and shall not throw exceptions.Ashall be an allocator (17.6.3.5). The copy constructor and destructor ofAshall not throw exceptions. WhenTisU[N],Y(*)[N]shall be convertible toT*; whenTisU[],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 and p20 as follows:
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:
Requires:Y*shall beconvertible tocompatible withT*.
Add a new paragraph before 20.7.2.2.1 [util.smartptr.shared.const] p33:
Requires:Y*shall be compatible withT*.
Change the line preceding 20.7.2.2.5 [util.smartptr.shared.obs] p1 as follows:
T*element_type* get() const noexcept;
Change 20.7.2.2.5 [util.smartptr.shared.obs] p4 as follows:
Remarks:WhenThis member function shall not participate in overload resolution whenTisvoid, 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.Tis an array type or cv-qualifiedvoid.
Change 20.7.2.2.5 [util.smartptr.shared.obs] p5, p6 and the preceding line as follows:
T*element_type* operator->() const noexcept;Requires:get() != 0.Returns:get().element_type& operator[](ptrdiff_t i) const noexcept;Requires:get() != 0 && i >= 0. IfTisU[N],i < N.Returns:get()[i].Remarks: This member function shall only participate in overload resolution whenTis an array type.
Change 20.7.2.2.9 [util.smartptr.shared.cast] p1, p2, p3 as follows:
Requires: The expressionstatic_cast<T*>(r.get())static_cast<T*>((U*)0)shall be well formed.
Returns:Ifris empty, an emptyshared_ptr<T>; otherwise, ashared_ptr<T>object that storesstatic_cast<T*>(r.get())and shares ownership withrshared_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(), wherewis the return value.
Change 20.7.2.2.9 [util.smartptr.shared.cast] p5, p6, p7 as follows:
Requires: The expressiondynamic_cast<T*>(r.get())dynamic_cast<T*>((U*)0)shall be well formed and 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 withrshared_ptr<T>(r, p);- Otherwise,
an emptyshared_ptr<T>objectshared_ptr<T>().
Postconditions:w.get() == dynamic_cast<T*>(r.get()), wherewis the return value.
Change 20.7.2.2.9 [util.smartptr.shared.cast] p9, p10, p11 as follows:
Requires: The expressionconst_cast<T*>(r.get())const_cast<T*>((U*)0)shall be well formed.
Returns:Ifris empty, an emptyshared_ptr<T>; otherwise, ashared_ptr<T>object that storesconst_cast<T*>(r.get())and shares ownership withrshared_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(), wherewis 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:
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.
— end