1

I'm trying to return a vector<unique_ptr> from a function, but I'm running into errors. I'm using MSVC 19.33.31630.

The comments to this question recommend returning by value, but this code:

std::vector<std::unique_ptr<int>> test1()
{
    std::vector<std::unique_ptr<int>> ret = { std::make_unique<int>(1) };
    return ret;
}

int main()
{
    std::vector<std::unique_ptr<int>> x = test1();
    std::unique_ptr<int>& ptrY = x.at(0);
    return 0;
}

yields this error:

Error C2280 'std::unique_ptr<int,std::default_delete<int>>::unique_ptr(const std::unique_ptr<int,std::default_delete<int>> &)': attempting to reference a deleted function

Returning by reference, as in this code:

std::vector<std::unique_ptr<int>>& test2()
{
    std::vector<std::unique_ptr<int>> ret = { std::make_unique<int>(1) };
    return ret;
}

int main()
{
    std::vector<std::unique_ptr<int>>& y = test2();
    std::cout << *(y.at(0)) << std::endl; 

    return 0;
}

yields the same error.

Why is this happening? Is the ownership of the unique_ptr not transferring properly?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Scienciser
  • 31
  • 1
  • 1
    Interesting fun fact: Here's the code without returning ANYTHING: https://godbolt.org/z/zo1WWseMz The `unique_ptr` is being copied into an `initializer_list` that's where the copy is being made. – user4581301 Oct 21 '22 at 04:58
  • Avoid making copies, so use `return std::make_unique()` or `auto ptr = std::make_unique(); .. do something with p .. return p;` And you could return `auto` from test 2 if you want. – Pepijn Kramer Oct 21 '22 at 04:59
  • 1
    `test2` is flat-out doomed to an ill fate as long as it returns by reference. Returning a reference to a local variable is a non-starter. The fact that `test1` and `test2` reportedly had the same error message is what made me look at it more closely. There is no copy on return with a return by reference, ergo if the same error crops up, the copy must be elsewhere. – user4581301 Oct 21 '22 at 05:08
  • Looking for a good duplicate [brought this question up](https://stackoverflow.com/q/9618268/4581301). I'm not comfortable closing as a dupe because this doesn't give an answer, but I don't see an answer. Even if you `move` into the `initializer_list`, the `initializer_list` is still getting copied into the `vector`. You might be stuck with a block of `emplace_back`s. – user4581301 Oct 21 '22 at 05:15
  • This looks like a viable duplicate (But I could be biased because it answers my question about why you can't move out of the `initializer_list` - DUH! It's `const`): [Any way to initialize a vector of unique_ptr?](https://stackoverflow.com/questions/25827435/any-way-to-initialize-a-vector-of-unique-ptr) – user4581301 Oct 21 '22 at 05:25

2 Answers2

0

Thanks @user4581301 for answering this in the comments. As best as I can tell:

  • test2() puts the std::vector on the stack which goes out of scope.
  • test1() ends up copying the unique_ptr into an initializer_list

The solution is to return by value, and initialize the vector with push_back/emplace_back instead of an initializer_list:

std::vector<std::unique_ptr<int>> test3()
{
    std::vector<std::unique_ptr<int>> ret;
    ret.push_back(std::make_unique<int>(1));
    return ret;
}

int main()
{
    std::vector<std::unique_ptr<int>> x = test3();
    std::unique_ptr<int>& ptrY = x.at(0); 
    std::cout << *ptrY;

    return 0;
}
Scienciser
  • 31
  • 1
  • I was slightly wrong. The `unique_ptr` is not being copied into the `initializer_list`. We could solve that with `std::vector> ret = { std::move( std::make_unique(1) ) };`. The real villain is `std::vector` can't `move` the items out of the `initializer_list` because the items in an `initializer_list` are `const`. – user4581301 Oct 21 '22 at 05:42
-3
std::vector<std::unique_ptr<int>> x = test1();

You're making a copy of the vector that test1 returns here, which in turn needs to copy all its content. That's what an assignment operator = does by default! Only after the copy is done, the return value of test1 will run out of scope and get destructed.

You're trying to copy unique_ptrs; can't copy a unique_ptr. So, this is correct.

Use std::move to move the values held by the vector.

Even better, don't use the assignment operator, but construct from the return value directly, which will use the move constructor (if the function returns by value).

Marcus Müller
  • 34,677
  • 4
  • 53
  • 94
  • 2
    thats not an assignment operator – Neil Butterworth Oct 21 '22 at 04:57
  • hm, what else would the `=` be, @NeilButterworth? – Marcus Müller Oct 21 '22 at 04:59
  • @MarcusMüller -- That is the copy constructor in this context. – PaulMcKenzie Oct 21 '22 at 05:00
  • 4
    @MarcusMüller when `=` is used in a variable declaration, ie `type var = value;`, it is just *syntax sugar*, the operation actually invokes copy construction, not assignment. It is equivalent to `type var(value);` And in modern C++, when the value comes from the return value of a function call, the copy you describe is optimized away so the function initializes the variable directly. – Remy Lebeau Oct 21 '22 at 05:01
  • 1
    The problem location isn't where getting the return value, it's where the vector construction inside the function. – fana Oct 21 '22 at 05:22
  • @NeilButterworth yes it is. – thedemons Oct 21 '22 at 05:27
  • @thedemons Yes, it is an assignment operator, but `=` is not *used* as an assignment operator in an initialization. See Remy's comment for the gory details. – user4581301 Oct 21 '22 at 05:33
  • @user4581301 oh yeah sorry I was confused about copy assignment vs constructor assignment – thedemons Oct 21 '22 at 05:43