Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 932
jaggedSpire Avatar asked May 28 '15 01:05

jaggedSpire


1 Answers

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.

like image 59
Praetorian Avatar answered Oct 06 '22 08:10

Praetorian