Reputation: 4513
[Followup to this question]
I've been dealing a little bit with smart pointers to c-style arrays recently. I ultimately wound up doing the recommended thing and using smart pointers to vectors instead, but during that period, I got a bit of advice: don't use a shared_ptr<T>
object to manage an array initially made with make_unique<T[]>
because it won't call delete[]
but rather delete
.
This didn't seem logical to me, and I checked both Coliru and the Standard:
This code:
#include <iostream>
#include <memory>
int main()
{
std::cout << "start!\n";
auto customArrayAllocator = [](unsigned int num){
std::cout << "custom array allocator\n";
return new int[num];
};
std::cout << "allocator constructed\n";
auto customArrayDeleter = [](int *ptr){
std::cout << "custom array deleter\n";
delete[] ptr;
};
std::cout << "deleter constructed\n";
std::unique_ptr<int[], decltype(customArrayDeleter)>
myUnique(customArrayAllocator(4), customArrayDeleter);
std::cout << "unique_ptr constructed\n";
std::shared_ptr<int>
myShared = std::move(myUnique);
std::cout << "shared_ptr constructed\n";
}
produces this output:
start!
allocator constructed
deleter constructed
custom array allocator
unique_ptr constructed
shared_ptr constructed
custom array deleter
Which seems to indicate that the unique_ptr<T[]>
's deleter is passed to the shared_ptr<T>
, as I expected.
From the C++14 Standard § 20.8.2.2.1 pg. 571 of doc, 585 of pdf
template shared_ptr(unique_ptr&& r);
Remark: This constructor shall not participate in overload resolution unless unique_ptr::pointer is convertible to T*.
Effects: Equivalent to shared_ptr(r.release(), r.get_deleter()) when D is not a reference type, otherwise shared_ptr(r.release(), ref(r.get_deleter())).
Exception safety: If an exception is thrown, the constructor has no effect.
If I'm reading that right, it means that a shared_ptr
object constructs itself from both the pointer and the deleter of a unique_ptr
. Furthermore, it's my understanding (from the answer to the original question) that the ::pointer
type of unique_ptr<T[]>
is T*
, which should be convertible to shared_ptr<T>::pointer
's T*
. So the deleter should just be copied right from the unique_ptr
object, right?
Did my test only work because it's not actually equivalent to the function of std::make_shared<T[]>
, or is the syntax
std::shared_ptr<T> mySharedArray = std::make_unique<T[]>(16);
a good, exception safe (and cleaner) alternative to
std::shared_ptr<T> mysharedArray(new T[16], [](T* ptr){delete[] ptr;});
and its ilk, when I am unable to use Boost's shared array and desire to avoid including either the vector
or the array
header with my code?
Upvotes: 10
Views: 2316
Reputation: 109119
Yes, your example is valid for the very reasons you've stated. unique_ptr::pointer
is int *
, and you're trying to pass ownership of that to a shared_ptr<int>
, so the converting constructor you've listed will participate in overload resolution, and will make a copy of the deleter (std::default_delete<int[]>
) because it's not a reference type.
So the following is valid, and will call delete[]
when the shared_ptr
reference count goes to zero
std::shared_ptr<T> mySharedArray = std::make_unique<T[]>(16);
Another way to write this, other than the lambda you've shown, is
std::shared_ptr<T> mySharedArray(new T[16], std::default_delete<int[]>());
which will result in mySharedArray
acquiring the same deleter as the previous line.
Upvotes: 7