shared_ptr to an array : should it be used?
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 expressiondelete[] p
, whenT
is an array type, ordelete p
, whenT
is not an array type, shall have well-defined behavior, and shall not throw exceptions.
...
Remarks: WhenT
is an array type, this constructor shall not participate in overload resolution unless the expressiondelete[] p
is well-formed and eitherT
isU[N]
andY(*)[N]
is convertible toT*
, orT
is
U[]
andY(*)[]
is convertible toT*
. ...
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
. IfT
isU[N]
,i < N
.
...
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.
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 typesstd::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 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*
.
shared_ptr to an array
You should create that shared_ptr
like that
std::shared_ptr<int> sp( new int[10], std::default_delete<int[]>() );
You must give other deleter to shared_ptr
You can't use std::make_shared
, because that function gives only 1 parameter, for create pointer on array you must create deleter too.
Or you can use too (like in comments , with array or with vector, which has own deleter)
std::shared_ptr<std::array<int,6>> ptr(std::make_shared<std::array<int, 6>>(std::array<int, 6>()));
How get particular element?
Like that
std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());
sp.get()[0] = 5;
std::cout << sp.get()[0] << std::endl;
Since C++17 supports shared_ptr of array, does this mean that an explicit deleter for T[] is no longer required in both ctor and reset?
You are correct, shared_ptr<T[]>
now naturally handles calling delete[]
properly.
http://eel.is/c++draft/util.smartptr.shared#const-5
Effects: When T is not an array type, constructs 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.
And as far as reset()
goes:
http://eel.is/c++draft/util.smartptr.shared#mod-3
Equivalent to shared_ptr(p).swap(*this).
Which will transfer the specification-required custom deleter.
Store shared pointer to array in vector
I feel like shared ownership is the wrong model here. Conceptually, why would you want your workers to continue to work on an array if no one else is observing the result anymore?
So I'd have the arrayVect
own the arrays and hand out pointers to the arrays to the workers. When it doesn't make sense to keep one of the arrays around, stop the worker first and then delete the array.
The easiest way to get that behavior is to make arrayVect
a std::vector<std::unique_ptr<std::array<char, MAX_LENGTH>>>
. Then the pointer to the underlying char[MAX_LENGTH]
array that you can pass to a worker can be obtained by calling arrayVect[idx].get().data()
.
By having the additional indirection through the unique_ptr
the pointers to the arrays remain valid even if the vector is resized.
EDIT: Here is an example how that can work with unique_ptr
s even though your workers also need a pointer to the array:
class Worker {
public:
Worker(std::array<char, MAX_SIZE>* array)
: _array{array} {
}
void perform_work() {
function_that_requires_c_arrays(_array->data()); // maybe also a size parameter?
}
private:
std::array<char, MAX_SIZE>* _array;
};
int main() {
std::vector<std::unique_ptr<std::array<char, MAX_SIZE>>> arrayVect;
arrayVect.emplace_back(std::make_unique<std::array<char, MAX_SIZE>>()));
Worker w{arrayVect.back().get()};
w.perform_work();
}
Accessing array of shared_ptr
The bracket notation is defined to work with pointer types (and you're right that, given array array
, the expression array
decays to an expression with such a type which points to the first element) but, despite its function, std::shared_ptr
is not a pointer type.
You would have to obtain the raw pointer first:
array.get()[n];
Where n
is, of course, a valid array subscript.
This is also the case with std::unique_ptr
(though note that, in that case, you do not need to supply your own deleter!).
Making and accessing shared ptr array
This is a very very bad idea. You are creating indeed an array of
10
int
s, but the default deleter usesdelete
instead ofdelete[]
to deallocate the memory. You either have to specify a custom deleter,std::shared_ptr<int> asd(new int[10]{0}, [](int* _p)
{
delete[] _p;
}); // also zero-initializesor use
std::unique_ptr<int[]> asd(new int[10]{0}); // no need for a deleter here
instead, as
unique_ptr
has an array specialization, and the deleter uses the properdelete[]
for this specialization. The examples above zero-initialize the dynamically allocated arrays. If you want other values, then dereference the pointers and set the values, as you'd do in a raw pointer situation. To access the value in the array pointed by ashared_ptr
, you cannot usearr[j]
syntax anymore, asshared_ptr
does not overloadoperator[]
. You need to get the managed raw pointer and use it like*(asd.get()+2) // this dereference the third element
or
asd.get()[2] // same as above
As you can see,
shared_ptr
is not really made for arrays. On the other hand,unique_ptr
overloadsoperator[]
for its managed array, so you can simply useasd[2]
to refer to the third element in the array.You cannot use
make_shared
, asmake_shared
does not allow you to specify the deleter.Simply create an array of 10
shared_ptr
from an initialshared_ptr
that points to your desiredint
std::shared_ptr<int> sp(new int{42});
std::array<std::shared_ptr<int>, 10> arr_sp;
for(auto& elem: arr_sp)
elem = sp;DO NOT construct all elements of the array from the same raw pointer, as you end up with multiple
shared_ptr
s managing the same raw pointer, which is always a bad idea.
Related Topics
How to Redirect Cin and Cout to Files
Why Does Modulus Division (%) Only Work With Integers
Undefined Reference to Static Constexpr Char[]
Why Doesn't a Simple "Hello World"-Style Program Compile With Turbo C++
Example For Boost Shared_Mutex (Multiple Reads/One Write)
Uninitialized Variable Behaviour in C++
Why Copy Constructor Is Not Called in This Case
Calling Objective-C Method from C++ Member Function
C++11 Return Value Optimization or Move
Initialization Order of Class Data Members
How to Use the Conditional (Ternary) Operator
How Does C++ Handle &&? (Short-Circuit Evaluation)
Generate Random Numbers Using C++11 Random Library