Can you make a std::shared_ptr manage an array allocated with new T[]?
c++, c++11, shared-ptr, smart-pointers
Solution
With C++17, `shared_ptr` can be used to manage a dynamically allocated array. The `shared_ptr` template argument in this case must be `T[N]` or `T[]`. So you may write
shared_ptr<int[]> sp(new int[10]);
From n4659, [util.smartptr.shared.const]
template<class Y> explicit shared_ptr(Y* p);
Requires: `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 have well-defined behavior, and shall not throw exceptions. ... Remarks: When `T` is an array type, this constructor shall not participate in overload resolution unless the expression `delete[] p` is well-formed and either `T` is `U[N]` and `Y(*)[N]` is convertible to `T*`, or `T` is `U[]` and `Y(*)[]` is convertible to `T*`. ...
To support this, the member type `element_type` is now defined as
using element_type = remove_extent_t<T>;
Array elements can be access using `operator[]`
element_type& operator[](ptrdiff_t i) const;
Requires: `get() != 0 && i >= 0`. If `T` is `U[N]`, `i < N`. ... Remarks: When `T` 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.
Prior to C++17, `shared_ptr` could not be used to manage dynamically allocated arrays. By default, `shared_ptr` will call `delete` on the managed object when no more references remain to it. However, when you allocate using `new[]` you need to call `delete[]`, and not `delete`, to free the resource.
In order to correctly use `shared_ptr` with an array, you must supply a custom deleter.
template< typename T >
struct array_deleter
{
void operator ()( T const * p)
{
delete[] p;
}
};
Create the shared_ptr as follows:
std::shared_ptr<int> sp(new int[10], array_deleter<int>());
Now `shared_ptr` will correctly call `delete[]` when destroying the managed object.
The custom deleter above may be replaced by
the `std::default_delete` partial specialization for array types
std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());
a lambda expression
std::shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });
Also, unless you actually need share onwership of the managed object, a `unique_ptr` is better suited for this task, since it has a partial specialization for array types.
std::unique_ptr<int[]> up(new int[10]); // this will correctly call delete[]
Changes introduced by the C++ Extensions for Library Fundamentals
Another pre-C++17 alternative to the ones listed above was provided by the Library Fundamentals Technical Specification, which augmented `shared_ptr` to allow it to work out of the box for the cases when it owns an array of objects. The current draft of the `shared_ptr` changes slated for this TS can be found in N4082. These changes will be accessible via the `std::experimental` namespace, and included in the `<experimental/memory>` header. A few of the relevant changes to support `shared_ptr` for arrays are:
— The definition of the member type `element_type` changes
typedef T element_type;
typedef typename remove_extent<T>::type element_type;
— Member `operator[]` is being added
element_type& operator[](ptrdiff_t i) const noexcept;
— Unlike the `unique_ptr` partial specialization for arrays, both `shared_ptr<T[]>` and `shared_ptr<T[N]>` will be valid and both will result in `delete[]` being called on the managed array of objects.
template<class Y> explicit shared_ptr(Y* p);
Requires: `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*`.
Problem
Can you make a `std::shared_ptr` point to an array? For example, ``` std::shared_ptr<int> sp(new int[10]); ``` If not, then why not? One reason I am already aware of is one can not increment/decrement the `std::shared_ptr`. Hence it cannot be used like a normal pointer to an array.