c++ - El compilador no falla al empujar un std:: unique_ptr en un std:: vector
smart-pointers stdvector (4)
Con C ++ 11 conseguimos mover constructores y valores semánticos.
std :: move (X) es solo un lanzamiento a un valor que convierte X en X &&, eso es todo. Luego de mover, ctor retoma el trabajo y mueve a los constructores, por lo general, "roban" los recursos en poder del argumento. unique_ptr tiene un ctor de movimiento.
Los valores de retorno de la función ya son un valor r (a menos que la función devuelva una referencia de valor l como se indica en @HolyBlackCat en los comentarios) que activará el ctor de movimiento sin necesidad de una conversión adicional. Y dado que move ctor está definido para unique_ptr, se compilará.
También la razón por la que v.push_back (p1); falla es: intenta llamar al constructor de copia con un lvalue y falla porque unique_ptr no tiene un ctor de copia.
Un
unique_ptr
no se puede reenviar en un
std::vector
ya que no se puede copiar, a menos que se use
std::move
.
Sin embargo, seamos una función que devuelva un
unique_ptr
, entonces, la operación
std::vector::push_back(F())
está permitida.
Hay un ejemplo a continuación:
#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)
está permitido, pero
(1)
no está permitido.
¿Es esto porque el valor devuelto se mueve de alguna manera implícita?
En
(2)
, ¿es realmente necesario usar
std::move
?
También vale la pena saber que también funcionaría debido a la capacidad del compilador para mover objetos que no se mueven explícitamente (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; }
std::vector::push_back()
tiene una sobrecarga que toma una referencia rvalue como entrada:
void push_back( T&& value );
El valor de retorno de
create()
es un temporal sin nombre, es decir, un rvalue, por lo que se puede pasar como está a
push_back()
sin necesidad de usar
std::move()
en él.
std::move()
es necesario solo cuando se pasa una variable nombrada, es decir, un valor l, donde se espera un valor r.
std::move(X)
significa esencialmente "aquí, trata a X como si fuera un objeto temporal".
create()
devuelve un
std::unique_ptr<A>
temporal
std::unique_ptr<A>
para comenzar, por lo que
move
es necesario.
Si quieres saber más, mira en las categorías de valor . Su compilador usa categorías de valor para determinar si una expresión se refiere a un objeto temporal ("rvalue") o no ("lvalue").
p1
es un lvalue, y
create()
es un rvalue.