unique_ptr<T>
no permite la construcción de copias, sino que admite la semántica de movimiento. Sin embargo, puedo devolver un unique_ptr<T>
de una función y asignar el valor devuelto a una variable.
#include <iostream>
#include <memory>
using namespace std;
unique_ptr<int> foo()
{
unique_ptr<int> p( new int(10) );
return p; // 1
//return move( p ); // 2
}
int main()
{
unique_ptr<int> p = foo();
cout << *p << endl;
return 0;
}
El código anterior compila y funciona según lo previsto. Entonces, ¿cómo es que esa línea 1
no invoca al constructor de la copia y produce errores de compilación? Si tuviera que usar la línea 2
en su lugar, tendría sentido (usar la línea también 2
funciona, pero no estamos obligados a hacerlo).
Sé que C ++ 0x permite esta excepción unique_ptr
ya que el valor de retorno es un objeto temporal que se destruirá tan pronto como la función salga, garantizando así la unicidad del puntero devuelto. Tengo curiosidad acerca de cómo se implementa esto, ¿se trata de un caso especial en el compilador o hay alguna otra cláusula en la especificación del lenguaje que explota?
fuente
unique_ptr
. La pregunta completa es que 1 y 2 son dos formas diferentes de lograr lo mismo.main
función salga, pero no cuandofoo
salga.Respuestas:
Sí, ver 12.8 §34 y §35:
Solo quería agregar un punto más de que la devolución por valor debería ser la opción predeterminada aquí porque un valor con nombre en la declaración de devolución en el peor de los casos, es decir, sin elisiones en C ++ 11, C ++ 14 y C ++ 17 se trata como un valor Entonces, por ejemplo, la siguiente función se compila con la
-fno-elide-constructors
banderaCon el indicador configurado en la compilación, hay dos movimientos (1 y 2) que suceden en esta función y luego uno más adelante (3).
fuente
foo()
también está a punto de ser destruido (si no se asignó a nada), al igual que el valor de retorno dentro de la función y, por lo tanto, tiene sentido que C ++ use un constructor de movimiento cuando lo haceunique_ptr<int> p = foo();
?std::unique_ptr
), existe una regla especial para tratar primero los objetos como valores. Creo que esto está completamente de acuerdo con lo que Nikola ha respondido.Esto no es específico de ninguna manera
std::unique_ptr
, pero se aplica a cualquier clase que sea móvil. Está garantizado por las reglas del idioma, ya que está regresando por valor. El compilador intenta eludir copias, invoca un constructor de movimientos si no puede eliminar copias, llama a un constructor de copias si no puede moverse y no puede compilar si no puede copiar.Si tuviera una función que acepte
std::unique_ptr
como argumento, no podría pasarle p. Tendría que invocar explícitamente el constructor de movimiento, pero en este caso no debería usar la variable p después de la llamada abar()
.fuente
p
no es temporal, el resultado defoo()
lo que se devuelve es; por lo tanto, es un valor r y se puede mover, lo que hacemain
posible la asignación . Diría que te equivocaste, excepto que Nikola parece aplicar esta regla ap
sí misma, lo cual ES un error.1
y Line2
? En mi opinión, es el mismo desde entonces cuando se construyep
enmain
, sólo se preocupa por el tipo de tipo de retorno defoo
, ¿verdad?unique_ptr no tiene el constructor de copia tradicional. En su lugar, tiene un "constructor de movimientos" que utiliza referencias rvalue:
Una referencia de valor r (el doble ampersand) solo se unirá a un valor r. Es por eso que obtiene un error cuando intenta pasar un lvalue unique_ptr a una función. Por otro lado, un valor que se devuelve desde una función se trata como un valor r, por lo que el constructor de movimiento se llama automáticamente.
Por cierto, esto funcionará correctamente:
El temporal unique_ptr aquí es un rvalue.
fuente
p
, "obviamente" un valor l , ser tratado como un valor r en la declaración returnreturn p;
en la definición defoo
. No creo que haya ningún problema con el hecho de que el valor de retorno de la función en sí se puede "mover".Creo que está perfectamente explicado en el ítem 25 de Effective Modern C ++ de Scott Meyers . Aquí hay un extracto:
Aquí, RVO se refiere a la optimización del valor de retorno , y si se cumplen las condiciones para el RVO significa devolver el objeto local declarado dentro de la función que esperaría hacer el RVO , que también se explica muy bien en el artículo 25 de su libro al referirse a el estándar (aquí el objeto local incluye los objetos temporales creados por la declaración de devolución). La mayor extracción del extracto es que se realiza una elisión de copia o
std::move
se aplica implícitamente a los objetos locales que se devuelven . Scott menciona en el ítem 25 questd::move
se aplica implícitamente cuando el compilador elige no eludir la copia y el programador no debe hacerlo explícitamente.En su caso, el código es claramente un candidato para RVO ya que devuelve el objeto local
p
y el tipo dep
es el mismo que el tipo de retorno, lo que da como resultado una elisión de copia. Y si el compilador elige no eludir la copia, por cualquier razón,std::move
se hubiera puesto en línea1
.fuente
Una cosa que no vi en otras respuestas esPara aclarar otras respuestas, hay una diferencia entre devolver std :: unique_ptr que se ha creado dentro de una función y una que se le ha dado a esa función.El ejemplo podría ser así:
fuente