Soy nuevo en mover semántica en C ++ 11 y no sé muy bien cómo manejar unique_ptr
parámetros en constructores o funciones. Considere esta clase haciendo referencia a sí misma:
#include <memory>
class Base
{
public:
typedef unique_ptr<Base> UPtr;
Base(){}
Base(Base::UPtr n):next(std::move(n)){}
virtual ~Base(){}
void setNext(Base::UPtr n)
{
next = std::move(n);
}
protected :
Base::UPtr next;
};
¿Es así como debo escribir funciones tomando unique_ptr
argumentos?
¿Y debo usarlo std::move
en el código de llamada?
Base::UPtr b1;
Base::UPtr b2(new Base());
b1->setNext(b2); //should I write b1->setNext(std::move(b2)); instead?
c++
arguments
c++11
unique-ptr
codablank1
fuente
fuente
Respuestas:
Estas son las posibles formas de tomar un puntero único como argumento, así como su significado asociado.
(A) Por valor
Para que el usuario pueda llamar a esto, debe realizar una de las siguientes acciones:
Tomar un puntero único por valor significa que está transfiriendo la propiedad del puntero a la función / objeto / etc. en cuestión. Después de que
newBase
se construye,nextBase
se garantiza que esté vacío . No eres el propietario del objeto y ya ni siquiera tienes un puntero. Se fue.Esto está garantizado porque tomamos el parámetro por valor.
std::move
en realidad no mueve nada; Es solo un elenco elegante.std::move(nextBase)
devuelve unBase&&
que es una referencia de valor rnextBase
. Eso es todo lo que hace.Debido a que
Base::Base(std::unique_ptr<Base> n)
toma su argumento por valor en lugar de por referencia de valor r, C ++ construirá automáticamente un temporal para nosotros. Crea un astd::unique_ptr<Base>
partir delBase&&
cual le dimos la funciónstd::move(nextBase)
. Es la construcción de este temporal lo que realmente mueve el valornextBase
al argumento de la funciónn
.(B) Por referencia de valor l no constante
Esto se debe invocar en un valor l real (una variable con nombre). No se puede llamar con un temporal como este:
El significado de esto es el mismo que el de cualquier otro uso de referencias no constantes: la función puede o no reclamar la propiedad del puntero. Dado este código:
No hay garantía de que
nextBase
esté vacío. Se puede estar vacía; Puede que no. Realmente depende de lo queBase::Base(std::unique_ptr<Base> &n)
quiera hacer. Por eso, no es muy evidente solo por la firma de la función lo que va a suceder; Tienes que leer la implementación (o la documentación asociada).Por eso, no sugeriría esto como una interfaz.
(C) Por referencia de valor l constante
No muestro una implementación, porque no puedes pasar de a
const&
. Al pasar aconst&
, está diciendo que la función puede acceder aBase
través del puntero, pero no puede almacenarla en ningún lado. No puede reclamar su propiedad.Esto puede ser útil. No necesariamente para su caso específico, pero siempre es bueno poder darle un puntero a alguien y saber que no puede (sin romper las reglas de C ++, como no descartarlo
const
) reclamar la propiedad del mismo. No pueden almacenarlo. Pueden pasarlo a otros, pero esos otros deben cumplir con las mismas reglas.(D) Por referencia de valor r
Esto es más o menos idéntico al caso "por referencia de valor l no constante". Las diferencias son dos cosas.
Usted puede pasar una temporal:
Usted debe utilizar
std::move
al pasar argumentos que no sean temporales.Este último es realmente el problema. Si ves esta línea:
Tiene una expectativa razonable de que, una vez completada esta línea,
nextBase
debería estar vacía. Debería haber sido movido de. Después de todo, tienes esostd::move
sentado allí, diciéndote que ha ocurrido un movimiento.El problema es que no lo ha hecho. No se garantiza que haya sido movido de. Es posible que se haya movido, pero solo lo sabrá mirando el código fuente. No se puede distinguir solo de la firma de la función.
Recomendaciones
unique_ptr
, tómela por valor.unique_ptr
durante la ejecución de esa función, tómela porconst&
. Alternativamente, pasar una&
oconst&
para el tipo real señalado, en vez de usar ununique_ptr
.&&
. Pero le recomiendo encarecidamente que no haga esto siempre que sea posible.Cómo manipular unique_ptr
No puedes copiar un
unique_ptr
. Solo puedes moverlo. La forma correcta de hacerlo es con lastd::move
función de biblioteca estándar.Si toma un
unique_ptr
valor, puede moverse libremente. Pero el movimiento en realidad no ocurre debido astd::move
. Tome la siguiente declaración:Esto es realmente dos declaraciones:
(nota: el código anterior no se compila técnicamente, ya que las referencias de valor r no temporales no son realmente valores r. Está aquí solo con fines de demostración).
El
temporary
es solo una referencia de valor roldPtr
. Es en el constructor denewPtr
donde ocurre el movimiento.unique_ptr
El constructor de movimientos (un constructor que toma un&&
sí mismo) es lo que hace el movimiento real.Si tiene un
unique_ptr
valor y desea almacenarlo en algún lugar, debe usarlostd::move
para hacer el almacenamiento.fuente
std::move
no nombra su valor de retorno. Recuerde que las referencias de valor con nombre son valores. ideone.com/VlEM3unique_ptr
; tal vez algunas otras personas que llaman necesitan la misma funcionalidad pero están reteniendo unashared_ptr
llamada en su lugar] (2) por referencia lvalue podría ser útil si la función llamada modifica el puntero, por ejemplo, agregar o quitar nodos (propiedad de lista) de una lista vinculada.unique_ptr
valores por referencia de valor (por ejemplo, al transformarlos enshared_ptr
). La justificación de esto podría ser que es un poco más eficiente (no se realiza ningún cambio a punteros temporales) mientras que le otorga exactamente los mismos derechos a la persona que llama (puede pasar valores o valores envueltosstd::move
, pero no valores enteros).Permítanme tratar de establecer los diferentes modos viables de pasar punteros a objetos cuya memoria es administrada por una instancia de la
std::unique_ptr
plantilla de clase; también se aplica a lastd::auto_ptr
plantilla de clase anterior (que creo que permite todos los usos que hace un puntero único, pero para los cuales además se aceptarán valores modificables donde se esperan valores, sin tener que invocarstd::move
), y en cierta medida tambiénstd::shared_ptr
.Como ejemplo concreto para la discusión, consideraré el siguiente tipo de lista simple
Las instancias de dicha lista (que no pueden compartir partes con otras instancias o ser circulares) son propiedad exclusiva de quien posea el
list
puntero inicial . Si el código del cliente sabe que la lista que almacena nunca estará vacía, también puede optar por almacenar la primeranode
directamente en lugar de alist
. Nonode
es necesario definir ningún destructor : dado que los destructores para sus campos se invocan automáticamente, el destructor de puntero inteligente eliminará recursivamente toda la lista una vez que finalice la vida útil del puntero o nodo inicial.Este tipo recursivo brinda la oportunidad de analizar algunos casos que son menos visibles en el caso de un puntero inteligente para datos sin formato. Además, las funciones mismas ocasionalmente proporcionan (recursivamente) un ejemplo de código de cliente también. El typedef para,
list
por supuesto, está sesgado haciaunique_ptr
, pero la definición podría cambiarse para usarauto_ptr
o en sushared_ptr
lugar sin mucha necesidad de cambiar a lo que se dice a continuación (en particular, con respecto a la seguridad de excepción garantizada sin la necesidad de escribir destructores).Modos de pasar punteros inteligentes
Modo 0: pase un puntero o argumento de referencia en lugar de un puntero inteligente
Si su función no está relacionada con la propiedad, este es el método preferido: no haga que tome un puntero inteligente. En este caso, su función no necesita preocuparse de quién es el propietario del objeto señalado o de qué manera se gestiona la propiedad, por lo que pasar un puntero sin formato es perfectamente seguro y la forma más flexible, ya que independientemente de la propiedad, un cliente siempre puede producir un puntero sin formato (ya sea llamando al
get
método o desde la dirección del operador&
).Por ejemplo, la función para calcular la longitud de dicha lista no debe ser un
list
argumento, sino un puntero sin formato:Un cliente que tiene una variable
list head
puede llamar a esta función comolength(head.get())
, mientras que un cliente que ha elegido almacenar unanode n
lista que no está vacía puede llamarlength(&n)
.Si se garantiza que el puntero no es nulo (que no es el caso aquí ya que las listas pueden estar vacías), uno podría preferir pasar una referencia en lugar de un puntero. Puede ser un puntero / referencia a non-
const
si la función necesita actualizar el contenido de los nodos, sin agregar o eliminar ninguno de ellos (esto último implicaría la propiedad).Un caso interesante que cae en la categoría del modo 0 es hacer una copia (profunda) de la lista; Si bien una función que realiza esto, por supuesto, debe transferir la propiedad de la copia que crea, no le preocupa la propiedad de la lista que está copiando. Por lo tanto, podría definirse de la siguiente manera:
Este código merece una mirada cercana, tanto para la pregunta de por qué se compila en absoluto (el resultado de la llamada recursiva a
copy
en la lista de inicializador se une al argumento de referencia rvalue en el constructor de movimiento deunique_ptr<node>
, akalist
, al inicializar elnext
campo del generadonode
), y para la pregunta de por qué es seguro para excepciones (si durante el proceso de asignación recursiva se agota la memoria y se produce una llamada denew
lanzamientostd::bad_alloc
, en ese momento un puntero a la lista parcialmente construida se mantiene de forma anónima en un tipo temporallist
creado para la lista de inicializadores, y su destructor limpiará esa lista parcial). Por cierto, uno debe resistir la tentación de reemplazar (como lo hice inicialmente) el segundonullptr
porp
, que después de todo se sabe que es nulo en ese punto: no se puede construir un puntero inteligente desde un puntero (en bruto) a constante , incluso cuando se sabe que es nulo.Modo 1: pase un puntero inteligente por valor
Una función que toma un valor de puntero inteligente como argumento toma posesión del objeto señalado de inmediato: el puntero inteligente que tenía la persona que llama (ya sea en una variable nombrada o en una temporal anónima) se copia en el valor del argumento en la entrada de la función y la persona que llama el puntero se ha convertido en nulo (en el caso de un temporal, la copia podría haberse omitido, pero en cualquier caso la persona que llama ha perdido el acceso al objeto señalado). Me gustaría llamar a esta llamada de modo en efectivo : la persona que llama paga por adelantado por el servicio llamado, y no puede hacerse ilusiones sobre la propiedad después de la llamada. Para aclarar esto, las reglas del lenguaje requieren que la persona que llama ajuste el argumento en
std::move
si el puntero inteligente se mantiene en una variable (técnicamente, si el argumento es un valor l); en este caso (pero no para el modo 3 a continuación), esta función hace lo que su nombre sugiere, es decir, mover el valor de la variable a temporal, dejando la variable nula.Para los casos en que la función llamada toma posesión incondicionalmente (roba) el objeto señalado, este modo se utiliza con
std::unique_ptr
ostd::auto_ptr
es una buena forma de pasar un puntero junto con su propiedad, lo que evita cualquier riesgo de pérdidas de memoria. No obstante, creo que solo hay muy pocas situaciones en las que el modo 3 a continuación no sea preferible (muy ligeramente) sobre el modo 1. Por esta razón, no proporcionaré ejemplos de uso de este modo. (Pero vea elreversed
ejemplo del modo 3 a continuación, donde se observa que el modo 1 funcionaría al menos también). Si la función toma más argumentos que solo este puntero, puede suceder que haya además una razón técnica para evitar el modo 1 (constd::unique_ptr
ostd::auto_ptr
): dado que se realiza una operación de movimiento real al pasar una variable de punterop
por la expresiónstd::move(p)
, no se puede suponer quep
tiene un valor útil al evaluar los otros argumentos (el orden de evaluación no está especificado), lo que podría conducir a errores sutiles; por el contrario, el uso del modo 3 asegura que nop
se realice ningún movimiento antes de la llamada a la función, por lo que otros argumentos pueden acceder de forma segura a un valorp
.Cuando se usa con
std::shared_ptr
, este modo es interesante porque con una definición de función única permite que la persona que llama elija si desea guardar una copia compartida del puntero mientras crea una nueva copia compartida para que la función la use (esto sucede cuando un valor se proporciona el argumento; el constructor de copia para los punteros compartidos utilizados en la llamada aumenta el recuento de referencia), o simplemente para dar a la función una copia del puntero sin retener uno o tocar el recuento de referencia (esto sucede cuando se proporciona un argumento de valor, posiblemente un valor envuelto en una llamada destd::move
). Por ejemploLo mismo podría lograrse definiendo por separado
void f(const std::shared_ptr<X>& x)
(para el caso lvalue) yvoid f(std::shared_ptr<X>&& x)
(para el caso rvalue), con cuerpos de función que difieren solo en que la primera versión invoca semántica de copia (usando construcción / asignación de copia cuando se usax
) pero la segunda versión mueve semántica (escribiendo en sustd::move(x)
lugar, como en el código de ejemplo). Entonces, para los punteros compartidos, el modo 1 puede ser útil para evitar la duplicación de código.Modo 2: pase un puntero inteligente por (modificable) referencia de valor
Aquí la función solo requiere tener una referencia modificable al puntero inteligente, pero no da ninguna indicación de lo que hará con él. Me gustaría llamar a este método llamar con tarjeta : la persona que llama asegura el pago dando un número de tarjeta de crédito. La referencia se puede utilizar para tomar posesión del objeto señalado, pero no es necesario. Este modo requiere proporcionar un argumento de valor variable modificable, correspondiente al hecho de que el efecto deseado de la función puede incluir dejar un valor útil en la variable del argumento. Una persona que llama con una expresión de valor que desea pasar a dicha función se vería obligada a almacenarla en una variable con nombre para poder realizar la llamada, ya que el lenguaje solo proporciona conversión implícita a una constantelvalue reference (refiriéndose a un temporal) de un rvalue. (A diferencia de la situación opuesta manejada por
std::move
, no es posible una conversión deY&&
aY&
, conY
el tipo de puntero inteligente; sin embargo, esta conversión podría obtenerse mediante una función de plantilla simple si realmente se desea; consulte https://stackoverflow.com/a/24868376 / 1436796 ). Para el caso en el que la función llamada tiene la intención de tomar posesión incondicionalmente del objeto, robando del argumento, la obligación de proporcionar un argumento lvalue está dando la señal incorrecta: la variable no tendrá un valor útil después de la llamada. Por lo tanto, debe preferirse el modo 3, que ofrece posibilidades idénticas dentro de nuestra función, pero pide a las personas que llaman que proporcionen un valor r, para tal uso.Sin embargo, hay un caso de uso válido para el modo 2, es decir, funciones que pueden modificar el puntero o el objeto señalado de una manera que implica propiedad . Por ejemplo, una función que antepone un nodo a un
list
proporciona un ejemplo de tal uso:Claramente, sería indeseable forzar el uso de las personas que llaman
std::move
, ya que su puntero inteligente aún posee una lista bien definida y no vacía después de la llamada, aunque es diferente a la anterior.Nuevamente, es interesante observar lo que sucede si la
prepend
llamada falla por falta de memoria libre. Entonces lanew
llamada se lanzarástd::bad_alloc
; en este momento, dado que no senode
pudo asignar, es seguro que la referencia de valor pasada (modo 3) destd::move(l)
todavía no se ha podido robar, ya que eso se haría para construir elnext
campo delnode
que no se pudo asignar. Por lo tanto, el puntero inteligente originall
aún conserva la lista original cuando se produce el error; esa lista será destruida adecuadamente por el destructor de puntero inteligente, o en caso del
que sobreviva gracias a unacatch
cláusula suficientemente temprana , aún conservará la lista original.Ese fue un ejemplo constructivo; Con un guiño a esta pregunta , también se puede dar el ejemplo más destructivo de eliminar el primer nodo que contiene un valor dado, si lo hay:
Nuevamente, la corrección es bastante sutil aquí. Notablemente, en la declaración final, el puntero que se encuentra
(*p)->next
dentro del nodo que se va a eliminar se desvincula (porrelease
, lo que devuelve el puntero pero hace que el original sea nulo) antesreset
(implícitamente) de destruir ese nodo (cuando destruye el valor anterior mantenidop
), asegurando que uno y solo un nodo se destruye en ese momento. (En la forma alternativa mencionada en el comentario, este tiempo se dejaría a los internos de la implementación del operador de asignación de movimiento de lastd::unique_ptr
instancialist
; el estándar dice 20.7.1.2.3; 2 que este operador debe actuar "como si fuera llamandoreset(u.release())
", por lo que el tiempo debe ser seguro aquí también.)Tenga en cuenta que
prepend
yremove_first
no puede ser llamado por los clientes que almacenan un local denode
variable para un siempre lista no vacía, y con razón, ya que las implementaciones dadas no podía trabajar para estos casos.Modo 3: pase un puntero inteligente por referencia de valor (modificable)
Este es el modo preferido para usar cuando simplemente toma posesión del puntero. Me gustaría llamar a este método llamar con cheque : la persona que llama debe aceptar renunciar a la propiedad, como si proporcionara efectivo, al firmar el cheque, pero el retiro real se pospone hasta que la función llamada realmente robe el puntero (exactamente como lo haría al usar el modo 2 ) La "firma del cheque" significa concretamente que las personas que llaman tienen que ajustar un argumento
std::move
(como en el modo 1) si es un valor l (si es un valor r, la parte "renunciar a la propiedad" es obvia y no requiere un código separado).Tenga en cuenta que técnicamente el modo 3 se comporta exactamente como el modo 2, por lo que la función llamada no tiene que asumir la propiedad; sin embargo, insistiría en que si existe alguna incertidumbre sobre la transferencia de propiedad (en uso normal), se debe preferir el modo 2 al modo 3, de modo que el uso del modo 3 implícitamente sea una señal para quienes llaman de que están renunciando a la propiedad. Uno podría replicar que solo pasar el argumento del modo 1 realmente indica la pérdida forzada de propiedad a las personas que llaman. Pero si un cliente tiene dudas sobre las intenciones de la función llamada, se supone que debe conocer las especificaciones de la función que se llama, lo que debería eliminar cualquier duda.
Es sorprendentemente difícil encontrar un ejemplo típico de nuestro
list
tipo que utilice el paso de argumentos en modo 3. Mover una listab
al final de otra listaa
es un ejemplo típico; sin embargoa
(que sobrevive y mantiene el resultado de la operación) se pasa mejor usando el modo 2:Un ejemplo puro de pasar el argumento del modo 3 es el siguiente que toma una lista (y su propiedad) y devuelve una lista que contiene los nodos idénticos en orden inverso.
Se puede llamar a esta función como
l = reversed(std::move(l));
para revertir la lista en sí misma, pero la lista revertida también se puede usar de manera diferente.Aquí el argumento se mueve inmediatamente a una variable local para la eficiencia (se podría haber usado el parámetro
l
directamente en lugar dep
, pero luego acceder a él cada vez implicaría un nivel adicional de indirección); por lo tanto, la diferencia con el paso del argumento del modo 1 es mínima. De hecho, utilizando ese modo, el argumento podría haber servido directamente como variable local, evitando así ese movimiento inicial; Esta es solo una instancia del principio general de que si un argumento pasado por referencia solo sirve para inicializar una variable local, uno podría pasarlo por valor y usar el parámetro como variable local.El uso del modo 3 parece ser defendido por el estándar, como lo demuestra el hecho de que todas las funciones de biblioteca proporcionadas que transfieren la propiedad de los punteros inteligentes usando el modo 3. Un caso convincente en particular es el constructor
std::shared_ptr<T>(auto_ptr<T>&& p)
. Ese constructor solía (instd::tr1
) tomar una referencia de valor de l modificable (al igual que elauto_ptr<T>&
constructor de copia) y, por lo tanto, podría llamarse con un valor deauto_ptr<T>
lp
como instd::shared_ptr<T> q(p)
, después de lo cualp
se ha restablecido a nulo. Debido al cambio del modo 2 al 3 en el paso de argumentos, este antiguo código ahora debe reescribirsestd::shared_ptr<T> q(std::move(p))
y continuará funcionando. Entiendo que al comité no le gustó el modo 2 aquí, pero tenían la opción de cambiar al modo 1, definiendostd::shared_ptr<T>(auto_ptr<T> p)
en cambio, podrían haberse asegurado de que el código antiguo funciona sin modificación, porque (a diferencia de los punteros únicos) los punteros automáticos pueden desreferenciarse silenciosamente a un valor (el objeto puntero se restablece a nulo en el proceso). Aparentemente, el comité prefirió tanto el modo de defensa 3 sobre el modo 1, que eligió romper activamente el código existente en lugar de usar el modo 1 incluso para un uso ya obsoleto.Cuándo preferir el modo 3 sobre el modo 1
El modo 1 es perfectamente utilizable en muchos casos, y podría preferirse sobre el modo 3 en los casos en que asumir la propiedad de otra manera tomaría la forma de mover el puntero inteligente a una variable local como en el
reversed
ejemplo anterior. Sin embargo, puedo ver dos razones para preferir el modo 3 en el caso más general:Es un poco más eficiente pasar una referencia que crear un indicador temporal y rechazar el puntero anterior (manejar efectivo es algo laborioso); En algunos escenarios, el puntero se puede pasar varias veces sin cambios a otra función antes de que sea realmente robado. Dicha aprobación generalmente requerirá escritura
std::move
(a menos que se use el modo 2), pero tenga en cuenta que esto es solo un reparto que en realidad no hace nada (en particular, no elimina la referencia), por lo que tiene un costo cero adjunto.En caso de que sea concebible que algo arroje una excepción entre el inicio de la llamada a la función y el punto donde (o alguna llamada contenida) realmente mueve el objeto señalado a otra estructura de datos (y esta excepción aún no está atrapada dentro de la función en sí) ), cuando se usa el modo 1, el objeto al que hace referencia el puntero inteligente se destruirá antes de que una
catch
cláusula pueda manejar la excepción (porque el parámetro de la función se destruyó durante el desenrollado de la pila), pero no así cuando se usa el modo 3. Este último proporciona el la persona que llama tiene la opción de recuperar los datos del objeto en tales casos (al detectar la excepción). Tenga en cuenta que el modo 1 aquí no causa una pérdida de memoria , pero puede conducir a una pérdida irrecuperable de datos para el programa, lo que también podría ser indeseable.Devolver un puntero inteligente: siempre por valor
Para concluir una palabra acerca de devolver un puntero inteligente, presumiblemente apuntando a un objeto creado para ser utilizado por la persona que llama. Este no es realmente un caso comparable con pasar punteros a funciones, pero para completar, me gustaría insistir en que en tales casos siempre regrese por valor (y no lo use
std::move
en lareturn
declaración). Nadie quiere obtener una referencia a un puntero que probablemente acaba de ser rechazado.fuente
unique_ptr
se ha movido o no, aún eliminará muy bien el valor si aún lo mantiene cada vez que se destruye o se reutiliza.unique_ptr
previene una pérdida de memoria (y, en cierto sentido, cumple su contrato), pero aquí (es decir, usando el modo 1) podría causar (en circunstancias específicas) algo que podría considerarse aún más dañino. , es decir, una pérdida de datos (destrucción del valor señalado) que podría haberse evitado utilizando el modo 3.Sí, tienes que hacerlo si tomas el
unique_ptr
valor por en el constructor. La simplicidad es algo agradable. Como nounique_ptr
se puede copiar (copia privada), lo que escribiste debería darte un error de compilación.fuente
Editar: esta respuesta es incorrecta, aunque, estrictamente hablando, el código funciona. Solo lo dejo aquí porque la discusión debajo es demasiado útil. Esta otra respuesta es la mejor respuesta dada en el momento en que edité esto por última vez: ¿Cómo paso un argumento unique_ptr a un constructor o una función?
La idea básica
::std::move
es que las personas que te están pasandounique_ptr
deberían usarlo para expresar el conocimiento de que sabenunique_ptr
que van a perder la propiedad.Esto significa que debería usar una referencia de valor de r
unique_ptr
en sus métodos, no ununique_ptr
sí mismo. De todos modos, esto no funcionará porque pasar un viejo simpleunique_ptr
requeriría hacer una copia, y eso está explícitamente prohibido en la interfazunique_ptr
. Curiosamente, el uso de una referencia de rvalue con nombre lo convierte nuevamente en un lvalue, por lo que también debe usar::std::move
dentro de sus métodos.Esto significa que sus dos métodos deberían verse así:
Entonces las personas que usan los métodos harían esto:
Como puede ver,
::std::move
expresa que el puntero va a perder la propiedad en el punto donde es más relevante y útil saberlo. Si esto sucediera de manera invisible, sería muy confuso para las personas que usan tu clase haberobjptr
perdido repentinamente la propiedad sin una razón aparente.fuente
Base fred(::std::move(objptr));
y noBase::UPtr fred(::std::move(objptr));
?std::move
en la implementación tanto del constructor como del método. E incluso cuando pasa por valor, la persona que llama aún debe usarstd::move
para pasar valores. La principal diferencia es que con el paso por valor esa interfaz deja en claro que se perderá la propiedad. Ver el comentario de Nicol Bolas sobre otra respuesta.debería ser mucho mejor como
y
debiera ser
con el mismo cuerpo
Y ... ¿qué hay
evt
dentrohandle()
?fuente
std::forward
aquí: siempreBase::UPtr&&
es un tipo de referencia rvalue y lo pasa como un rvalue. Ya se reenvió correctamente.std::move
unique_ptr
valor por, se le garantiza que se llamó a un constructor de movimiento sobre el nuevo valor (o simplemente que se le otorgó un valor temporal). Esto asegura que launique_ptr
variable que tiene el usuario ahora está vacía . Si lo toma en su&&
lugar, solo se vaciará si su código invoca una operación de movimiento. A su manera, es posible que la variable de la que el usuario no haya sido movido. Lo que hace que el uso del usuario seastd::move
sospechoso y confuso. El usostd::move
siempre debe garantizar que se haya movido algo .A la respuesta más votada. Prefiero pasar por rvalue reference.
Entiendo cuál es el problema que puede causar el pasar por la referencia rvalue. Pero dividamos este problema en dos lados:
Debo escribir código
Base newBase(std::move(<lvalue>))
oBase newBase(<rvalue>)
.El autor de la biblioteca debe garantizar que realmente moverá el unique_ptr para inicializar el miembro si quiere ser el propietario.
Eso es todo.
Si pasa por referencia de valor, solo invocará una instrucción de "movimiento", pero si pasa por valor, son dos.
Sí, si el autor de la biblioteca no es experto en esto, no puede mover unique_ptr para inicializar el miembro, pero es el problema del autor, no usted. Pase lo que pase por valor o referencia de valor, ¡su código es el mismo!
Si está escribiendo una biblioteca, ahora sabe que debe garantizarla, así que hágalo, pasar por referencia de valor es una mejor opción que el valor. El cliente que usa su biblioteca simplemente escribirá el mismo código.
Ahora, para tu pregunta. ¿Cómo paso un argumento unique_ptr a un constructor o una función?
Sabes cuál es la mejor opción.
http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html
fuente