184

Just a small query regarding shared_ptr.

Is it a good practice to use shared_ptr pointing to an array? For example,

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 shared_ptr. Hence it can not be used like a normal pointer to an array.

Toby Speight
  • 23,550
  • 47
  • 57
  • 84
tshah06
  • 2,225
  • 3
  • 16
  • 21
  • 2
    FWIT, you may also consider just using `std::vector`. You'd have to be careful to pass the array around using references so that you don't make copies of it. Syntax for accessing data is cleaner than shared_ptr, and resizing it is very very easy. And you get all the STL goodness should you ever want it. – Nicu Stiurca Nov 30 '12 at 11:57
  • 6
    If the size of the array is determined at compile time, you might also consider using `std::array`. It is *almost* the same as a raw array, but with proper semantics for use in most library components. Especially objects of that type are destroyed with `delete`, not `delete[]`. And unlike `vector`, it stores the data directly in the object, so you get no extra allocation. – celtschk Apr 19 '15 at 13:23

2 Answers2

284

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

Praetorian
  • 100,267
  • 15
  • 224
  • 307
  • 9
    +1, remark: There is also Boost's [`shared-array`](http://www.boost.org/doc/libs/1_51_0/libs/smart_ptr/shared_array.htm). – jogojapan Oct 25 '12 at 05:29
  • Thanks for your answer, Praetorian. Can you please tell me how can i assign different values to the int array which shared_ptr is pointing to? – tshah06 Oct 25 '12 at 05:34
  • 5
    @tshah06 [`shared_ptr::get`](http://en.cppreference.com/w/cpp/memory/shared_ptr/get) returns a pointer to the managed object. So you can use it as `sp.get()[0] = 1; ... sp.get()[9] = 10;` – Praetorian Oct 25 '12 at 05:52
  • 56
    ALT: `std::shared_ptr sp( new int[10], std::default_delete() );` see also http://en.cppreference.com/w/cpp/memory/default_delete – yohjp Oct 25 '12 at 06:24
  • There's always the option to declare a class that contains the array as a member variable, and pass around objects of the class via a shared_ptr. That avoids the delete-vs-delete[] issue, with no overhead except a few extra lines of code. – Jeremy Friesner Oct 03 '13 at 14:10
  • 2
    @Jeremy If the size is known at compile time there's no need to write a class for that, `std::shared_ptr>` should be enough. – Praetorian Oct 03 '13 at 15:26
  • 13
    Why does `unique_ptr` get that partial specialization but `shared_ptr` does not? – Adam May 30 '14 at 22:00
  • Those are not C++14 changes, those are Library Fundamentals TS changes – Cubbi Sep 02 '14 at 17:19
  • @Cubbi Thank you, fixed. – Praetorian Sep 04 '14 at 00:26
  • 1
    I'm confused by this answer. I cannot tell whether I need to do something special or not. – Andrew Dec 24 '15 at 06:39
  • @Praetorian I cited your answer [in a question about the T(N) syntax](http://stackoverflow.com/questions/40447401/why-allow-shared-ptrtn). If you have any thoughts on that, feedback would be appreciated! – monkey0506 Nov 06 '16 at 08:35
  • Could I also use `std::make_shared(10)` when using c++17? – krjw Feb 05 '19 at 15:25
30

A possibly easier alternative that you might be able to use is shared_ptr<vector<int>>.

Peter G.
  • 13,888
  • 6
  • 51
  • 75
Timmmm
  • 68,359
  • 51
  • 283
  • 367
  • 5
    Yes it is. Or a vector is a superset of an array - it has the same in-memory representation (plus metadata) but is resizable. There aren't really any situations where you want an array but can't use a vector. – Timmmm Dec 30 '14 at 21:01
  • 2
    The difference, here, is that the vector size is anymore static, and the access to the data will be done with a double indirection. If performance is not the critical issue, this works, otherwise sharing an array may have its own reason. – Emilio Garavaglia Oct 21 '15 at 08:14
  • 4
    Then you can probably use `shared_ptr>`. – Timmmm Oct 21 '15 at 09:33
  • 10
    The other difference is that it's *slightly* bigger and slower than a raw array. Generally not really an issue but let's not pretend that 1 == 1.1. – Andrew Dec 24 '15 at 06:47
  • 2
    There are situations where the source of the data in the array means that it is unwieldily or unnecessary to convert to a vector; such as when getting a frame from a camera. (Or, that's my understanding, anyway) – Narfanator Jan 30 '16 at 02:28