Cannot move std::unique_ptr with NULL deleter to std::shared_ptr?

我的梦境 提交于 2019-12-02 04:19:23

问题


I want to move a NULL std::unique_ptr to a std::shared_ptr, like so:

std::unique_ptr<float> test = nullptr;
std::shared_ptr<float> test2 = std::move(test);

As far as I know it should be legal to do so, and it does run fine in Visual Studio 2015 and GCC.

However, I can't do the same with a std::unique_ptr which has a deleter declaration, like so:

std::unique_ptr<float,void(*)(float*)> test = nullptr;
std::shared_ptr<float> test2 = std::move(test);

The code above will not compile in visual studio and will trigger the static assert failure "error C2338: unique_ptr constructed with null deleter pointer".

I can use a std::function deleter instead, in which case the static assert failure can be circumvented:

std::unique_ptr<float,std::function<void(float*)>> test = nullptr;
std::shared_ptr<float> test2 = std::move(test);

In this case the code compiles fine, but I get an abort as soon as the last std::shared_ptr copy of test2 is destroyed.

Why are the latter two cases so problematic?

Strangely enough, if I change the type of test2 from std::shared_ptr to std::unique_ptr, the second case still triggers the static assert failure, but both case 1 and case 3 work just fine:

{
    std::unique_ptr<float> test = nullptr;
    std::unique_ptr<float> test2 = std::move(test); // Works fine
}
{
    //std::unique_ptr<float,void(*)(float*)> test = nullptr; // triggers a static assert failure
    //std::unique_ptr<float,void(*)(float*)> test2 = std::move(test);
}
{
    std::unique_ptr<float,std::function<void(float*)>> test = nullptr;
    std::unique_ptr<float,std::function<void(float*)>> test2 = std::move(test); // Works fine
}

回答1:


The unique_ptr constructor you are trying to use, which default-constructs the deleter, is ill-formed (before C++17) or disabled by SFINAE (as of C++17) if the deleter type is a pointer, in order to stop you from accidentally creating a unique_ptr whose deleter is itself a null pointer. If you really want to create such a unique_ptr, you can do so by explicitly passing a null deleter:

std::unique_ptr<float,void(*)(float*)> test(nullptr, nullptr);

This unique_ptr object is not very useful, because it can't delete anything.

By using a null std::function deleter, you've told the compiler "yes, I really want to shoot myself in the foot". Of course, when the last std::shared_ptr is destroyed, the null std::function is invoked, and undefined behaviour occurs. What else did you expect?




回答2:


I'll echo Brian's answer and add that in situations like this where the function shouldn't be null, you can use a function reference, which are nonnullable like all C++ references, instead of function pointers.

void delete_float(float *f) {delete f;}

using Deleter = void(float*);

// Function pointers
constexpr void(*fptr)(float*) = delete_float;
constexpr Deleter *fptr_typedef = delete_float;
constexpr auto fptr_auto = delete_float;
constexpr auto *fptr_auto2 = delete_float;

// Function references
constexpr void(&fref)(float*) = delete_float;
constexpr Deleter &fref_typedef = delete_float;
constexpr auto &fref_auto = delete_float;

The one gotcha you need to keep in mind with function references is that lambdas implicitly convert to function pointers, but not function references, so you need to use the dereference operator * to convert a lambda to a function reference.

const Deleter *fptr_lambda = [](float *f) {delete f;};
// const Deleter &fref_lambda = [](float *f) {delete f;}; // error
const Deleter &fref_lambda_fixed = *[](float *f) {delete f;};


来源:https://stackoverflow.com/questions/49329645/cannot-move-stdunique-ptr-with-null-deleter-to-stdshared-ptr

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!