usar - retornar dos valores en c++
ReenvĂo de valores de retorno. Es necesario std:: forward? (3)
Estoy escribiendo una biblioteca que envuelve muchas funciones y métodos de otra biblioteca. Para evitar el copiado de los valores de retorno, estoy aplicando std::forward
así:
template<class T>
T&& wrapper(T&& t) {
f(t); // t passed as lvalue
return std::forward<T>(t);
}
f
devuelve el void
y toma T&&
(o sobrecargado en valor). El envoltorio siempre devuelve el parámetro de envoltorios y el valor devuelto debe conservar la falta de argumentos del argumento. ¿Realmente necesito usar std::forward
a return
? ¿RVO lo hace superfluo? ¿El hecho de que sea una referencia (R o L) la hace superflua? ¿Es necesario si return no es la última declaración de función (dentro de algunos si)?
Es discutible si el wrapper()
debe devolver void
o T&&
, porque la persona que llama tiene acceso al valor evaluado a través de arg (que es referencia, R o L). Pero en mi caso, necesito devolver valor para que wrapper()
se pueda usar en expresiones.
Puede ser irrelevante para la pregunta, pero se sabe que las funciones f
no roban de t
, por lo que el primer uso de std::forward
en f(std::forward<T>(t))
es superfluo y fue eliminado por yo.
He escrito una pequeña prueba: https://gist.github.com/3910503
La prueba muestra que el retorno de T
no creado crea una copia adicional en gcc48 y clang32 con -O3 (RVO no se activa).
Además, no pude obtener un mal comportamiento de UB en:
auto&& tmp = wrapper(42);
No prueba nada de causa porque es un comportamiento indefinido (si es UB).
¡Dependiendo de lo que pase esta función, resulta en un comportamiento indefinido! Más precisamente, si pasa un valor no-l, es decir , un valor de r, a esta función, el valor al que hace referencia la referencia devuelta estará obsoleto.
Además, T&&
no es una "referencia universal", aunque el efecto es algo así como una referencia universal ya que T
puede deducirse como T&
o T const&
. El caso problemático es cuando se deduce como T
: los argumentos se pasan como temporales y mueren después de que la función regresa pero antes de que algo pueda obtener una referencia a ella.
El uso de std::forward<T>(x)
está limitado a, bueno, reenviar objetos al llamar a otra función: lo que entró como un temporal parece un lvalue dentro de la función. El uso de std::forward<T>(x)
permite que x
vea como un temporal si se incluyó como uno - y, por lo tanto, permite pasar de x
al crear el argumento de la función llamada.
Cuando devuelves un objeto desde una función, hay algunos escenarios que quizás quieras cuidar, pero ninguno de ellos involucra a std::forward()
:
- Si el tipo es realmente una referencia, ya sea
const
o non-const
, no desea hacer nada con el objeto y simplemente devolver la referencia. - Si todas las declaraciones de
return
usan la misma variable o todas están usando un temporal, se puede usar elision copiar / mover y se usará en compiladores decentes. Dado que la elección de copiar / mover es una optimización, no necesariamente sucede, sin embargo. - Si siempre se devuelve la misma variable local o un temporal, se puede mover desde si hay un constructor de movimiento, de lo contrario se copiará el objeto.
- Cuando se devuelven diferentes variables o cuando la devolución involucra una expresión, es posible que se sigan devolviendo las referencias, pero elision de copiar / mover no funcionará, ni será posible pasar directamente del resultado si no es temporal. En estos casos, debe usar
std::move()
para poder moverse desde el objeto local.
En la mayoría de estos casos, el tipo producido es T
y debe devolver T
lugar de T&&
. Si T
es un tipo de valor T
el resultado puede no ser un tipo de valor l, sin embargo, y puede ser necesario eliminar la calificación de referencia del tipo de retorno. En el escenario que usted preguntó específicamente sobre el tipo T
funciona.
En el caso de que sepa que t
no estará en un estado de mudanza después de la llamada a f
, sus dos opciones un tanto sensatas son:
return
std::forward<T>(t)
con el tipoT&&
, que evita cualquier construcción pero permite escribir, por ejemplo,auto&& ref = wrapper(42);
, que dejaref
una referencia colgantereturn
std::forward<T>(t)
con el tipoT
, que, en el peor de los casos, solicita una construcción de movimiento cuando el parámetro es un valor r. Esto evita el problema anterior para los valores predefinidos, pero potencialmente roba valores de x
En todos los casos necesitas std::forward
. No se considera elision de copia porque t
es siempre una referencia.
No, no necesita usar std::forward
mejor no devolver la referencia de valor r en absoluto porque puede evitar la optimización NRVO. Puede leer más sobre la semántica de movimientos en este artículo: Article