En C++ es muy común lo que considero un antipatrón que usa const T&
como una forma inteligente de decir T
cuando se trata de parámetros. Sin embargo, un valor y una referencia (no importa si const o no) son dos cosas completamente diferentes y siempre y ciegamente el uso de referencias en lugar de valores puede dar lugar a errores sutiles.
La razón es que cuando se trata de referencias debe tener en cuenta dos cuestiones que no se presentan con valores: vida y aliasing.
Sólo como ejemplo un lugar donde se aplica esta anti-patrón es la biblioteca estándar sí mismo, donde std::vector<T>::push_back
acepta como parámetro un const T&
en lugar de un valor y esto puede reprimir por ejemplo, en código como:
std::vector<T> v;
...
if (v.size())
v.push_back(v[0]); // Add first element also as last element
Este código es una bomba de tiempo porque std::vector::push_back
quiere una referencia constante, pero hacer push_back puede requerir una reasignación y si eso ocurre significa que después de la reasignación la referencia recibida ya no sería válida (de por vida problema) y usted ingresa el Comportamiento de comportamiento no definido.
Los problemas de alias son también una fuente de problemas sutiles si se utilizan referencias de referencias en lugar de valores.He sido mordido por ejemplo, código de este tipo:
struct P2d
{
double x, y;
P2d(double x, double y) : x(x), y(y) {}
P2d& operator+=(const P2d& p) { x+=p.x; y+=p.y; return *this; }
P2d& operator-=(const P2d& p) { x-=p.x; y-=p.y; return *this; }
};
struct Rect
{
P2d tl, br;
Rect(const P2d& tl, const P2d& br) : tl(tl), bt(br) {}
Rect& operator+=(const P2d& p) { tl+=p; br+=p; return *this; }
Rect& operator-=(const P2d& p) { tl-=p; br-=p; return *this; }
};
El código parece a primera vista bastante seguro, P2d
es un punto bidimensional, Rect
es un rectángulo y suma/resta un punto significa traducir el rectángulo .
Sin embargo, para volver a traducir el rectángulo en el origen escriba myrect -= myrect.tl;
el código no funcionará porque el operador de traducción se ha definido aceptando una referencia que (en ese caso) hace referencia a un miembro de la misma instancia.
Esto significa que después de actualizar el superior izquierda con la tl -= p;
superior izquierda será (0, 0)
como debería, sino también p
se convertirá, al mismo tiempo, porque (0, 0)
p
es sólo una referencia al miembro superior izquierda y así la actualización de abajo hacia la esquina derecha no funcionará porque la traducirá por (0, 0)
, lo que hace básicamente nada.
No se deje engañar por pensar que una referencia constante es como un valor debido a la palabra const
. Esa palabra existe solo para darle errores de compilación si intenta cambiar el objeto referenciado usando esa referencia, pero no significa que el objeto al que se hace referencia es constante. Más específicamente, el objeto al que hace referencia una referencia constante puede cambiar (por ejemplo, debido a aliasing) e incluso puede desaparecer mientras lo está utilizando (de por vida problema).
En const T&
la palabra const expresa una propiedad de la referencia, no de la referencia objeto: es la propiedad que hace imposible su uso para cambiar el objeto. Probablemente readonly habría sido un nombre mejor como const tiene IMO el efecto psicológico de impulsar la idea de que el objeto va a ser constante mientras usa la referencia.
Por supuesto, puede obtener aceleraciones impresionantes mediante el uso de referencias en lugar de copiar los valores, especialmente para las clases grandes. Pero siempre debe pensar en los alias y los problemas de duración cuando use referencias, ya que debajo de la tapa son solo indicadores de otros datos. Para referencias de tipos de datos "nativos" (ints, dobles, punteros), sin embargo, en realidad van a ser más lentos que los valores y no hay nada que ganar al usarlos en lugar de valores.
También una referencia constante siempre significa problemas para el optimizador como el compilador se ve obligado a ser paranoico y cada vez que se ejecuta ningún código desconocido que hay que suponer que todos los objetos referenciados pueden tener ahora un valor diferente (const
por unos medios de referencia absolutamente NADA para el optimizador; esa palabra está allí solo para ayudar a los programadores; personalmente no estoy tan seguro de que sea una gran ayuda, pero esa es otra historia).
¿cómo podría ser optimizado por el compilador? Si la función está enlineada, podría ser el compilador, pero para el caso general en el que no lo esté debe devolver exactamente lo que usted dijo que sería (excepto en algunas excepciones), ya que podría llamarlo desde una unidad de traducción diferente utilizando el prototipo especificado, por lo que debe invocarse con int/const int & (en el nivel de ensamblaje, pasar un int a algo esperando int y no es una buena idea). Combine eso con el hecho de que const int & es probable que sea más lento que int (debido a la adición de deref y la copia barata) y ahora debería saber qué hacer. – Grizzly
Para el optimizador, 'const T &' es simplemente 'T &', en otras palabras, solo un puntero constante a un objeto T posiblemente cambiante. La palabra 'const' solo es vista por los programadores. – 6502
@ 6502: Bueno, y por el front-end del compilador, que interviene si el programador no presta atención al 'const'. – sbi