c++ - tipos - Variables de miembros de referencia como miembros de la clase
que es una propiedad de una clase (5)
¿Hay un nombre para describir este idioma?
En UML se llama agregación. Difiere de la composición en que el objeto miembro no es propiedad de la clase de referencia. En C ++ puede implementar la agregación de dos maneras diferentes, a través de referencias o punteros.
Supongo que es para evitar la posible gran sobrecarga de copiar un gran objeto complejo?
No, esa sería una razón realmente mala para usar esto. La razón principal para la agregación es que el objeto contenido no es propiedad del objeto contenedor y, por lo tanto, sus vidas no están vinculadas. En particular, la duración del objeto referenciado debe sobrevivir a la referencia. Puede haber sido creado mucho antes y podría vivir más allá del final de la vida útil del contenedor. Además de eso, el estado del objeto al que se hace referencia no está controlado por la clase, pero puede cambiar externamente. Si la referencia no es const
, entonces la clase puede cambiar el estado de un objeto que vive fuera de él.
¿Es esto generalmente una buena práctica? ¿Hay algún inconveniente en este enfoque?
Es una herramienta de diseño. En algunos casos será una buena idea, en algunos no lo será. La falla más común es que la vida útil del objeto que contiene la referencia nunca debe exceder el tiempo de vida del objeto al que se hace referencia. Si el objeto adjunto utiliza la referencia después de que se destruyó el objeto al que se hace referencia, tendrá un comportamiento indefinido. En general, es mejor preferir la composición a la agregación, pero si la necesita, es una herramienta tan buena como cualquier otra.
En mi lugar de trabajo, veo este estilo ampliamente utilizado:
#include <iostream>
using namespace std;
class A
{
public:
A(int& thing) : m_thing(thing) {}
void printit() { cout << m_thing << endl; }
protected:
const int& m_thing; //usually would be more complex object
};
int main(int argc, char* argv[])
{
int myint = 5;
A myA(myint);
myA.printit();
return 0;
}
¿Hay un nombre para describir este idioma? Supongo que es para evitar la posible gran sobrecarga de copiar un gran objeto complejo?
¿Es esto generalmente una buena práctica? ¿Hay algún inconveniente en este enfoque?
C ++ proporciona un buen mecanismo para administrar el tiempo de vida de un objeto a través de constructos de clase / estructura. Esta es una de las mejores características de C ++ sobre otros lenguajes.
Cuando tiene variables miembro expuestas a través de ref o puntero, viola la encapsulación en principio. Este modismo permite al consumidor de la clase cambiar el estado de un objeto de A sin que (A) tenga conocimiento o control sobre él. También permite al consumidor aferrarse a un ref / puntero al estado interno de A, más allá del tiempo de vida del objeto de A. Este es un mal diseño. En su lugar, la clase se puede refactorizar para mantener un ref / puntero al objeto compartido (no lo posee) y estos se pueden establecer usando el constructor (Mandate the life time rules). La clase del objeto compartido puede diseñarse para soportar multiprocesamiento / concurrencia según sea el caso.
Las referencias de los miembros generalmente se consideran malas. Hacen la vida difícil en comparación con los indicadores de miembros. Pero no es particularmente inusual, ni es un idioma o cosa especial. Solo es aliasing.
¿Hay un nombre para describir este idioma?
No hay un nombre para este uso, simplemente se conoce como "Referencia como miembro de la clase" .
Supongo que es para evitar la posible gran sobrecarga de copiar un gran objeto complejo?
Sí y también escenarios en los que desea asociar el tiempo de vida de un objeto con otro.
¿Es esto generalmente una buena práctica? ¿Hay algún inconveniente en este enfoque?
Depende de tu uso Usar cualquier función de idioma es como "elegir caballos para cursos" . Es importante tener en cuenta que cada característica ( casi todas ) de idioma existe porque es útil en algunos escenarios.
Hay algunos puntos importantes a tener en cuenta al usar referencias como miembros de la clase:
- Debe asegurarse de que el objeto referido se garantiza que existe hasta que exista su objeto de clase.
- Debe inicializar el miembro en la lista de inicializadores de miembros de constructor. No puede tener una inicialización lenta , que podría ser posible en el caso de un miembro del puntero.
- El compilador no generará el
operator=()
asignación de copiaoperator=()
y tendrá que proporcionar uno usted mismo. Es engorroso determinar qué acción tomará su operador en tal caso. Entonces, básicamente, su clase se vuelve no asignable . - Las referencias no pueden ser
NULL
ni estar hechas para referirse a ningún otro objeto. Si necesita volver a colocarlo, no es posible con una referencia como en el caso de un puntero.
Para la mayoría de los propósitos prácticos (a menos que esté realmente preocupado por el uso de memoria alta debido al tamaño del miembro) basta con tener una instancia de miembro, en lugar de puntero o miembro de referencia. Esto le ahorra mucha preocupación acerca de otros problemas que los miembros de referencia / puntero traen consigo a expensas del uso de memoria adicional.
Si debe usar un puntero, asegúrese de usar un puntero inteligente en lugar de un puntero sin formato. Eso haría su vida mucho más fácil con punteros.
Se llama inyección de dependencia mediante inyección de constructor : la clase A
obtiene la dependencia como argumento para su constructor y guarda la referencia a la clase dependiente como una variable privada.
Hay una introducción interesante en wikipedia .
Para const-correctness escribiría:
using T = int;
class A
{
public:
A(const T &thing) : m_thing(thing) {}
// ...
private:
const T &m_thing;
};
pero un problema con esta clase es que acepta referencias a objetos temporales:
T t;
A a1{t}; // this is ok, but...
A a2{T()}; // ... this is BAD.
Es mejor agregar (requiere C ++ 11 al menos):
class A
{
public:
A(const T &thing) : m_thing(thing) {}
A(const T &&) = delete; // prevents rvalue binding
// ...
private:
const T &m_thing;
};
De todos modos si cambias el constructor:
class A
{
public:
A(const T *thing) : m_thing(*thing) { assert(thing); }
// ...
private:
const T &m_thing;
};
es casi seguro que no tendrá un puntero a un temporal .
Además, dado que el constructor toma un puntero, es más claro para los usuarios de A
que necesitan prestar atención a la duración del objeto que pasan.
Algunos temas relacionados son:
- ¿Debo preferir los punteros o las referencias en los datos de los miembros?
- Usar referencia como miembros de la clase para dependencias
- GotW # 88
- Prohibir la vinculación de rvalue a través de un constructor a una referencia de referencia de miembro