Compiler doesn't fail when pushing back a std::unique_ptr into a std::vector
Asked Answered
R

4

23

An unique_ptr cannot be pushed back into a std::vector since it is non-copyable, unless std::move is used. However, let F be a function that returns a unique_ptr, then the operation std::vector::push_back(F()) is allowed. There is an example below:

#include <iostream>
#include <vector>
#include <memory>

class A {
  public:
    int f() { return _f + 10; }

  private:
    int _f = 20;
};

std::unique_ptr<A> create() { return std::unique_ptr<A>(new A); }


int main() {
  std::unique_ptr<A> p1(new A());

  std::vector< std::unique_ptr<A> > v;

  v.push_back(p1); // (1) This fails, should use std::move

  v.push_back(create()); // (2) This doesn't fail, should use std::move?

  return 0;
}

(2) is allowed, but (1) is not. Is this because the returned value is moved somehow implicitly?

In (2), is it actually necessary to use std::move?

Rheology answered 7/7, 2019 at 20:37 Comment(0)
D
35

std::move(X) essentially means "here, treat X as if it was a temporary object".

create() returns a temporary std::unique_ptr<A> to begin with, so move is unnecessary.


If you want to know more, look into the value categories. Your compiler uses value categories to determine if an expression refers to a temporary object ("rvalue") or not ("lvalue").

p1 is an lvalue, and create() is an rvalue.

Dennadennard answered 7/7, 2019 at 20:40 Comment(1)
Although true, your answer lacks the part that explains why and how push_back allows this behavior which is the part that can help people understand how they can implement and allow such code to work with their own APIsRickety
K
8

std::vector::push_back() has an overload that takes an rvalue reference as input:

void push_back( T&& value );

The return value of create() is an unnamed temporary, ie an rvalue, so it can be passed as-is to push_back() without needing to use std::move() on it.

std::move() is needed only when passing a named variable, ie an lvalue, where an rvalue is expected.

Keeper answered 7/7, 2019 at 21:3 Comment(0)
K
6

With C++11 we got move constructors and rvalues semantics.

std::move(X) is just a cast to a rvalue which converts X to X&& that is it. Than move ctor takes the job over and move constructors typically "steal" the resources held by the argument. unique_ptr have a move ctor.

Function return values are already a rvalue(unless the function returns an lvalue reference as indicated by @HolyBlackCat in comments) which will trigger the move ctor without needing any extra cast. And since move ctor is defined for unique_ptr it will compile.

Also the reason why v.push_back(p1);failing is: you try to call copy constructor with an lvalue and it fails because unique_ptr does not have a copy ctor.

Karriekarry answered 7/7, 2019 at 20:56 Comment(0)
C
2

It is also worth knowing that it would also work due to compiler ability to move objects that are not move explicitly (NRVO)

#include <iostream>
#include <vector>
#include <memory>

class A {
  public:
    int f() { return _f + 10; }

  private:
    int _f = 20;
};

std::unique_ptr<A> create() {
    std::unique_ptr<A> x (new A);
    return x; 

}


int main() {
  std::unique_ptr<A> p1(new A());

  std::vector< std::unique_ptr<A> > v;

  //v.push_back(p1); // (1) This fails, should use std::move

  v.push_back(create()); // (2) This doesn't fail, should use std::move?

  return 0;
}
Carolecarolee answered 8/7, 2019 at 20:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.