Why can't a pointer be automatically converted into a unique_ptr when returning it?

15,639

Solution 1

The answer is two-fold. All the other answers, including a OP's self-answer, addressed only one half of it.

The pointer cannot be automatically converted because:

  • unique_ptr's constructor from a pointer is declared explicit, thus considered by the compiler only in explicit contexts. This is done so to prevent accidental dangerous conversions, where a unique_ptr can hijack a pointer and delete it without programmer's knowledge. In general, not only for unique_ptr, it is considered a good practice to declare all single-argument constructors as explicit to prevent accidental conversions.
  • return statement is considered by the standard an implicit context, and thus the explicit constructor is not applicable. There was an ongoing discussion if this decision is right, reflected in EWG issue 114, including links to several proposals: two versions of proposals to make return explicit by Herb Sutter (N4029, N4074), and two "responses", arguing not to do so: N4094 by Howard Hinnant and Ville Voutilainen and N4131 by Filip Roséen. After several discussions and polls the issue was closed as NAD - not a defect.

Currently, there are several workarounds:

return std::unique_ptr<int>{p};

or

return std::unique_ptr<int>(p);

In c++14, you can use auto-deduction of function return type as well:

auto get_it() {
    auto p = new int;
    return std::unique_ptr<int>(p);
}

Update: added a link to committee issue for the second point.

Solution 2

Because implicit construction of unique_ptr from naked pointer would be very error-prone.

Just construct it explicitly:

std::unique_ptr<int> get_it() {
        auto p = new int;
        return std::unique_ptr<int>(p);
}

Solution 3

Because std::unique_ptr takes ownership of the pointer, and you definitely don't want to get your raw pointer deleted accidentally.

If that would be possible, then:

void give_me_pointer(std::unique_ptr<int>) { /* whatever */ }

int main() {
    int *my_int = new int;
    give_me_pointer(my_int);
    // my_int is dangling pointer
}

Solution 4

Because the conversion, or casting, requires an appropriate cast operator (on the source type) or constructor (on the target type). In this case it must be the following constructor of unique_ptr:

explicit unique_ptr( pointer p );

Which has the explicit keyword. your get_it() attempts implicit conversion, which explicit prevents. Instead, you have to construct the unique_ptr explicitly, as suggested by @Stas and @VincentSavard :

std::unique_ptr<int> get_it() {
        auto p = new int;
        return std::unique_ptr<int>(p);
}

or even, if we want to only say unique_ptr once...

auto get_it() {
        auto p = new int;
        return std::unique_ptr<int>(p);
}
Share:
15,639

Related videos on Youtube

einpoklum
Author by

einpoklum

Made my way from the Olympus of Complexity Theory, Probabilistic Combinatorics and Property Testing to the down-to-earth domain of Heterogeneous and GPU Computing, and now I'm hoping to bring the gospel of GPU and massive-regularized parallelism to DBMS architectures. I've post-doc'ed at the DB architecture group in CWI Amsterdam to do (some of) that. I subscribe to most of Michael Richter's critique of StackOverflow; you might want to take the time to read it. If you listen closely you can hear me muttering "Why am I not socratic again already?"

Updated on June 06, 2022

Comments

  • einpoklum
    einpoklum almost 2 years

    Let me pose my question through an example.

    #include <memory>
    
    std::unique_ptr<int> get_it() {
            auto p = new int;
            return p;
    }
    
    int main() {
            auto up ( get_it() );
            return 0;
    }
    

    This fails to compile with the following error:

    a.cpp:5:9: error: could not convert ‘p’ from ‘int*’ to ‘std::unique_ptr<int>’
      return p;
             ^
    

    Why isn't there an automatic conversion from a raw pointer to a unique one here? And what should I be doing instead?

    Motivation: I understand it's supposed to be good practice to use smart pointers for ownership to be clear; I'm getting a pointer (which I own) from somewhere, as an int* in this case, and I (think I) want it in a unique_ptr.


    If you're considering commenting or adding your own answer, please address Herbert Sutter's arguments for this to be possible in proposal N4029.

    • Kerrek SB
      Kerrek SB over 8 years
      Because then things like int x; return &x; would compile.
    • einpoklum
      einpoklum over 8 years
      @KerrekSB: I can also do int *p = nullptr; *p = 123; and that will compile. Anyway, not to be too argumentative - I'll ask - what should I do, then?
    • Vincent Savard
      Vincent Savard over 8 years
      You make one from your pointer, e.g. std::unique_ptr<int>(p).
    • einpoklum
      einpoklum over 8 years
      @VincentSavard: But isn't that exactly what I'm doing by returning p as an std::unique_ptr<int>?
    • Vincent Savard
      Vincent Savard over 8 years
      No, you're returning p, which is of type int*, not std::unique_ptr<int>. Maybe you do not understand what auto does.
    • Joe
      Joe over 8 years
      return std::make_unique<int>(); std::make_unique
    • einpoklum
      einpoklum over 8 years
      @VincentSavard: The return type is std::unique_ptr<int>, I can only return that. I mean, the int* is supposed to be cast. Just like if I returned 3.5 in a function returning int.
    • einpoklum
      einpoklum over 8 years
      @Joe: Assume you can't avoid p. Somehow an int* p gets created and set (in a magical way which I've left out of the MWE).
    • Vincent Savard
      Vincent Savard over 8 years
      And that's why the compiler doesn't compile this code. Is your question "Why isn't p implicitly cast to std::unique_ptr?"
    • einpoklum
      einpoklum over 8 years
      @VincentSavard: If that were the case, I should not be able to compile int foo() { return 3.5; } and need to do int foo() { return (int)3.5;} - but I don't need that.
    • Vincent Savard
      Vincent Savard over 8 years
      Because implicit conversion from double to int is defined. You're mixing different concepts.
    • Kerrek SB
      Kerrek SB over 8 years
      @einpoklum: Still, that's a different level of wrong. Your p is not dereferenceable, which is your fault. My &x is a perfectly valid int pointer.
    • Kerrek SB
      Kerrek SB over 8 years
    • Kerrek SB
      Kerrek SB over 8 years
  • einpoklum
    einpoklum over 8 years
    I can't say I understand how it would be error prone in a function returning a unique_ptr. Can you elaborate a bit more?
  • Stas
    Stas over 8 years
    E.g. foo(unique_ptr<int> p) {} in such case would be able to steal a passed naked pointer, and destroy the object.
  • einpoklum
    einpoklum over 8 years
    Your example does not involve a function returning a unique_ptr, so - I don't follow...
  • Stas
    Stas over 8 years
    Explicitness of an object construction is defined by a constructor definition. So, you can't do that implicitly when return the object, but explicitly in other cases.
  • einpoklum
    einpoklum over 8 years
    So, you're saying this is related to the explicit keyword?
  • Stas
    Stas over 8 years
    Yes. unique_ptr constructor from naked pointer is defined as explicit.
  • erip
    erip over 8 years
    This answers OP's question well - raw pointers cannot be implicitly converted to smart pointers.
  • einpoklum
    einpoklum over 8 years
    That's not the reason IMO. get_it returns a unique pointer, it is obvious returning a pointer from means transferring ownership.
  • GingerPlusPlus
    GingerPlusPlus over 8 years
    Good point, but I don't think C++ is smart enough to distinguish between this case and case in the question. So, both are allowed, or both are forbidden.
  • erip
    erip over 8 years
    Forgive me if this is a silly question, but why not just make_unique?
  • einpoklum
    einpoklum over 8 years
    @erip: Because this is an MWE. Actually we have auto p = do_stuff_which_returns_a_raw_pointer()
  • erip
    erip over 8 years
    Right, but you could simply do auto get_it() { auto p = new int; return std::make_unique<int>(p); }, could you not? I would think that forwarding would be preferred to explicit ctor call.
  • einpoklum
    einpoklum over 8 years
    @erip: In this example, yes. But, you know, I could just int main() { return 0;} since I don't do anything with p...
  • erip
    erip over 8 years
    I think you're missing my point. I've edited my comment to be more reflective of my intent.
  • einpoklum
    einpoklum over 8 years
    @erip That's exactly what the bottom of my answer suggests.
  • erip
    erip over 8 years
    Errr... No. There's a difference between constructing and forwarding an object.
  • erip
    erip over 8 years
    Also, why are you referring to yourself as OP? :) Typically self-answers should add something that other answers haven't. I think your answer is covered by @Stas 's answer.
  • ildjarn
    ildjarn over 8 years
    @einpoklum : This is indeed the reason.
  • einpoklum
    einpoklum over 8 years
    @erip: I think it would be more confusing if I wrote "oh, I can do this instead of that". Anyway, no, my answer is not covered by what Stas wrote; and I don't agree with part of his answer.
  • erip
    erip over 8 years
    Maybe not, but definitely covered the comments which is part of @Stas 's answer.
  • einpoklum
    einpoklum over 8 years
    Thank you for bringing Mr. Sutter's proposal and the EWG discussion to my attention, that is most illuminating (and I have to also say - standing in contrast to some people's dismissive attitude).
  • einpoklum
    einpoklum over 8 years
    @erip: (shrugs) you're kind of splitting hairs here. I wrote up another answer because I want to express the way I understood it. This won't be the accepted answer anyway.
  • Brian Bi
    Brian Bi over 5 years
    "it is considered a good practice to declare all single-argument constructors as explicit to prevent accidental conversions" --- who considers this a good practice? The standard library itself doesn't follow this rule. For example, std::shared_ptr<T> is implicitly constructible from std::unique_ptr<T>.