Const ref en C++

Const ref en C++


tips
programacion c++ unreal

Antes de empezar, si bien lo que voy a escribir acá es puntualmente sobre C++ (y su aplicación en Unreal), el concepto puede ser extendido prácticamente a todo lenguaje.

Hace poco me crucé con un código que iba algo así como:

void NombreMetodo(const FString& param1, const float& param2, const bool& param3)

Muchas veces escuchamos que todo lo que es por referencia es mejor, después de todo evita crear una copia de la variable que se pasó como argumento, y si es “const” entonces mucho mejor. ¿Esto es así?

Lamento ser el portador de malas noticias: no, no siempre.

Si bien en la gran mayoría de los casos va a ser “mejor” (o lo correcto) y si bien es preferible hacer todo “ref-const” y no pasar por valor, algo que deberíamos pasar por referencia o no pasar como const algo que no tenemos ninguna intención de modificar, la realidad es que hay casos donde esto no es correcto, básicamente porque destruye lo que el programador quería lograr en un principio: maximizar recursos.

Y no me estoy refiriendo a las situaciones lógicas como:

  • Queríamos modificar el valor del parámetro dentro del cuerpo del método/función (const daría error de compilación)
  • Queríamos que el valor del parámetro modificado no modificara al valor de la variable originalmente pasada como argumento (pasar por referencia no nos permite, al menos directamente, ese comportamiento).

Me refiero a situaciones donde no queríamos una ni otra cosa, y, sin embargo, sigue siendo lo de menor rendimiento, esa forma, ¿Por qué?

Veamos… cuando un valor se pasa por referencia no estamos pasando el valor, estamos pasando un puntero que apunta a la ubicación en memoria de la variable (después de todo la referencia es un alias que simplemente está referenciando a un puntero, un espacio de memoria).

Un puntero tiene un tamaño que varía dependiendo de la arquitectura del SO, pero que es siempre el mismo para la arquitectura dada. En una arquitectura de 64 bits (la más común hoy por hoy) un puntero ocupa 64 bits que es lo mismo que 8 bytes (es cierto que pueden existir excepciones a la regla y que el compilador puede jugar un papel en esto, pero vamos a mantenernos en las generales de la ley).

Si nos detenemos nuevamente a mirar los parámetros de nuestro método hay 2 en los que quiero hacer especial énfasis:

const float& param2,
const bool& param3

¿Cuánto ocupa un float en C++? 4 bytes

¿Y un bool? Esta definido por la implementación (con sizeof() pueden saber cuanto es en la suya, minimo 1 byte), pero para este caso en particular volvamosnos locos y digamos que son 4 bytes.

Como pueden ver no estamos ganando rendimiento (en este caso en el uso de memoria), además de vernos obligados a tener que “lidiar” con una referencia.

Más aún, podemos seguir sumando problemas innecesarios (esta vez si pasamos por referencia, pero no puede ser const), como lo explica “The Cherno” en esta parte de un video.

¿Por qué no pasar simplemente por valor? No hay un “por qué” real, sería la mejor práctica en este caso.

Lo mismo podemos decir de estructuras (no solo hay que pensar en esto con los primitivos) cuyas variables miembros sumen menos de 8 bytes (y no tengan un copy constructor que haga “cosillas raras” o algo por el estilo).

Como regla general, no utilizo referencias en ningún tipo built-in por “debajo” del tamaño de un double, ni en estructuras que cumplan las condiciones mencionadas. Mención especial para los valores “out” que necesito retornar de la función, claramente los necesitamos pasar por referencia (no const). Pero como ya repetí varias veces, no pretendo caer en todos los casos especiales donde, aun con lo ya mencionado, sería lo mejor pasar por referencia.

Por supuesto, en la gran mayoría de los casos vamos a estar utilizando la referencia y (cuando corresponda) const. La idea no es que demonizar la práctica (y como ya dije, preferible “errar” para este lado y no para el otro). La idea es simplemente no caer en los martillos de oro y en los “porque yo vi que alguien lo escribió así debe estar bien”, por lo general nunca me encontré con “fórmulas” que funcionen en el 100% de los casos y siempre razonar lo que estamos haciendo lleva a mejores conclusiones, aunque no sea más que para ahorrar 4 bytes de memoria.

Creditos:
Imagen de DCStudio en Freepik