Компилятор не терпит неудачу при возврате 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;
}