Document Number: N3640
Programming Language C++
Library Working Group
 
Peter Dimov, <pdimov@pdimov.com>
 
2013-04-15

Extending shared_ptr to Support Arrays

This paper proposes adding array support to shared_ptr, via the syntax shared_ptr<T[]> and shared_ptr<T[N]>.

Motivation

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).

Complexity

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.

reinterpret_pointer_cast

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.

Proposed Text

(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:
  typedef T typename 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, T element_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 of shared_ptr shall be CopyConstructible, CopyAssignable, and LessThanComparable, allowing their use in standard containers. Specializations of shared_ptr shall be contextually convertible to bool, allowing their use in boolean expressions and declarations in conditions. The template parameter T of shared_ptr may 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 type Y* is said to be compatible with a type T* when either Y* is convertible to T* or Y is U[N] and T is U cv [], where cv is some combination of cv-qualifiers.

Change 20.7.2.2.1 [util.smartptr.shared.const] p3 as follows:

Requires: p shall be convertible to T*. Y shall be a complete type. The expression delete p shall be well formed, shall have well defined behavior, and shall not throw exceptions. Y shall be a complete type. The expression delete[] p, when T is an array type, or delete p, when T is not an array type, shall be well-formed, shall have well defined behavior, and shall not throw exceptions. When T is U[N], Y(*)[N] shall be convertible to T*; when T is U[], Y(*)[] shall be convertible to T*; otherwise, Y* shall be convertible to T*.

Change 20.7.2.2.1 [util.smartptr.shared.const] p4 as follows:

Effects: When T is not an array type, cConstructs a shared_ptr object that owns the pointer p. Otherwise, constructs a shared_ptr that owns p and a deleter of an unspecified type that calls delete[] p.

Change 20.7.2.2.1 [util.smartptr.shared.const] p7 as follows:

Exception safety: If an exception is thrown, delete p is called when T is not an array type, delete[] p otherwise.

Change 20.7.2.2.1 [util.smartptr.shared.const] p8 as follows:

Requires: p shall be convertible to T*. D shall be CopyConstructible. The copy constructor and destructor of D shall not throw exceptions. The expression d(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 of A shall not throw exceptions. When T is U[N], Y(*)[N] shall be convertible to T*; when T is U[], Y(*)[] shall be convertible to T*; otherwise, Y* shall be convertible to T*.

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, T element_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 unless Y* is implicitly convertible to compatible with T*.

Change 20.7.2.2.1 [util.smartptr.shared.const] p23 as follows:

Requires: Y* shall be convertible to compatible with T*.

Add a new paragraph before 20.7.2.2.1 [util.smartptr.shared.const] p33:

Requires: Y* shall be compatible with T*.

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: When T is void, 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. This member function shall not participate in overload resolution when T is an array type or cv-qualified void.

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. If T is U[N], i < N.
Returns: get()[i].
Remarks: This member function shall only participate in overload resolution when T is an array type.

Change 20.7.2.2.9 [util.smartptr.shared.cast] p1, p2, p3 as follows:

Requires: The expression static_cast<T*>(r.get()) static_cast<T*>((U*)0) shall be well formed.
Returns: If r is empty, an empty shared_ptr<T>; otherwise, a shared_ptr<T> object that stores static_cast<T*>(r.get()) and shares ownership with r shared_ptr<T>(r, static_cast<typename shared_ptr<T>::element_type*>(r.get())).
Postconditions: w.get() == static_cast<T*>(r.get()) and w.use_count() == r.use_count(), where w is the return value.

Change 20.7.2.2.9 [util.smartptr.shared.cast] p5, p6, p7 as follows:

Requires: The expression dynamic_cast<T*>(r.get()) dynamic_cast<T*>((U*)0) shall be well formed and shall have well defined behavior.
Effects:
Postconditions: w.get() == dynamic_cast<T*>(r.get()), where w is the return value.

Change 20.7.2.2.9 [util.smartptr.shared.cast] p9, p10, p11 as follows:

Requires: The expression const_cast<T*>(r.get()) const_cast<T*>((U*)0) shall be well formed.
Returns: If r is empty, an empty shared_ptr<T>; otherwise, a shared_ptr<T> object that stores const_cast<T*>(r.get()) and shares ownership with r shared_ptr<T>(r, const_cast<typename shared_ptr<T>::element_type*>(r.get())).
Postconditions: w.get() == const_cast<T*>(r.get()) and w.use_count() == r.use_count(), where w 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 expression reinterpret_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:
  typedef T typename 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 unless Y* is implicitly convertible to compatible with T*.

Thanks to Glen Fernandes, whose contribution of boost::make_shared for arrays prompted the boost::shared_ptr additions proposed herein.

— end