Search code examples
c++shared-ptrc++14unique-ptr

Initialization of shared_ptr<T> from unique_ptr<T[]>


[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?


Solution

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