[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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With