Компилятор не терпит неудачу при возврате std :: unique_ptr в std :: vector

unique_ptr не может быть передан обратно в std::vector так как он не подлежит копированию, если только не используется std::move. Однако, если F - функция, которая возвращает unique_ptr, то операция std::vector::push_back(F()) разрешена. Ниже приведен пример:

#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) разрешено, но (1) нет. Это потому, что возвращаемое значение перемещается как-то неявно?

В (2) действительно ли необходимо использовать std::move?

Ответы

Ответ 1

std::move(X) сути означает "здесь, относитесь к X как к временному объекту".

create() возвращает временный std::unique_ptr<A> для начала, поэтому move не требуется.


Если вы хотите узнать больше, посмотрите на категории значений. Ваш компилятор использует категории значений, чтобы определить, относится ли выражение к временному объекту ("rvalue") или нет ("lvalue").

p1 является lvalue, а create() является rvalue.

Ответ 2

std::vector::push_back() имеет перегрузку, которая принимает ссылку на значение в качестве входного:

void push_back( T&& value );

Возвращаемое значение create() является безымянным временным, то есть rvalue, поэтому его можно передать как есть push_back() без необходимости использовать std::move() для него.

std::move() требуется только при передаче именованной переменной, то есть lvalue, где ожидается rvalue.

Ответ 3

С С++ 11 мы получили конструкторы перемещения и семантику значений.

std :: move (X) - это просто приведение к r-значению, которое преобразует X в X &&. Чем Move ctor берет на себя работу и перемещает конструкторы, как правило, "крадет" ресурсы, удерживаемые аргументом. unique_ptr есть ход ctor.

Возвращаемые значения функции уже являются rvalue (если только функция не возвращает ссылку на lvalue, как указано @HolyBlackCat в комментариях), которая запускает ctor перемещения без необходимости дополнительного приведения. И так как Move Ctor определен для unique_ptr, он скомпилируется.

Также причина сбоя v.push_back (p1); заключается в следующем: вы пытаетесь вызвать конструктор копирования с lvalue, и это не удается, потому что unique_ptr не имеет копии ctor.

Ответ 4

Также стоит знать, что это также будет работать из-за способности компилятора перемещать объекты, которые не перемещаются явно (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;
}