4

Tengo una duda, que llevo dando vueltas unos días, y no logro entender como trabaja o en que me estoy equivocando.

Punto de partida:

Supongamos que tenemos la siguiente funcíon:

 ArrayWrapper f(ArrayWrapper arr){
      return arr
 }

Lo que hace mi compilador:

1- Al ser paso por valor llama al constructor copia, obtiene arr, y me devuelve un objeto temporal arr.

2- LLegamos al return, y como arr es temporal, y la función devuelve por valor, pues en este caso llama al constructor move y me devuelve mi objeto.

Ahora vienen mis dudas:

a) Si fuera mi return: return ArrayWrapper(arr);

  • Comportamiento del compilador:

    LLama al constructor copia, obtiene arr, y vuelve a llamar al constructor copia. (ArrayWrapper(arr), es temporal o eso pensaba).

  • Lo que yo esperaba que al ser arr temporal, en ArrayWrapper(arr) se llamara al constructor move devolviendo un objeto temporal, y luego el return al tener que devolver una copia por valor, volviera a llamar al constructor move.

¿Por qué el compilador llama al constructor copia una vez que ya ha obtenido arr, y no llama a constructor move dos veces, una para obtener el objeto temporal ArrayWrapper(arr) y la segunda para devolverlo en el return?

eferion
  • 49,291
  • 5
  • 30
  • 72
Jcpardo
  • 435
  • 2
  • 8

1 Answers1

2

¿Por qué el compilador llama al constructor copia una vez que ya ha obtenido arr, y no llama a constructor move dos veces, una para obtener el objeto temporal ArrayWrapper(arr) y la segunda para devolverlo en el return?

arr puede ser todo lo temporal que quieras... pero el constructor move, para ser llamado, necesita un R-value y arr es un L-value.

Para forzar la llamada al constructor move tienes que convertir tu L-value en un R-value y para eso puedes usar std::move:

return ArrayWrapper(std::move(arr));

Ahora bien, en tu primera prueba:

return arr;

Has observado como el programa acaba llamando directamente al constructor move ¿Pero no habíamos dicho que arr es un L-value?

Efectivamente. arr sigue siendo un L-value pero aquí entran en juego otros factores:

  • arr es una variable local de la función
  • arr no es estática
  • La instrucción en cuestion es un return

Cuando se cumplen estos requisitos (y existe el constructor move) entra en juego una optimización que pretende reducir la sobrecarga de las instrucciones return. En este caso se entiende que arr no va a seguir existiendo después del return, por lo que no hay ningún inconveniente en convertirlo de forma implícita en un R-value y poder llamar así al constructor move.

El caso que nos ha llevado a la pregunta:

return ArrayWrapper(arr);

No cumple estos requisitos porque antes del return se está creando un objeto adicional de tipo ArrayWrapper. Un código equivalente sería:

ArrayWrapper temp(arr);
return temp;

Luego, en el momento de crear temp, arr aun no ha llegado al final de su vida útil (y tampoco se encuentra afectado por el return), luego se invocará al constructor copia ya que aquí no existen optimizaciones.

Por otro lado ten en cuenta que en C++ hay otra optimización referente a los return que es la omisión de copia. Este tema ya se ha tratado en StackOverflow, por lo que para no repetirme te remito a esta otra pregunta.

eferion
  • 49,291
  • 5
  • 30
  • 72