2012-09-12 17 views
45

En mi lugar de trabajo que veo este estilo utilizado ampliamente: -miembro de referencia como miembros de la clase

#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?

¿Esta es una buena práctica en general? ¿Hay algún inconveniente en este enfoque?

+2

Un posible escollo es si el objeto al que hace referencia en la variable miembro se destruye en otro lugar e intenta acceder a él a través de su clase – mathematician1975

Respuesta

60

¿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 por 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 objeto grande y complejo?

No, esa sería una muy mala razón 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, la clase puede cambiar el estado de un objeto que vive fuera de él.

¿Es esta 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 el objeto al que se hace referencia se destruyó, 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.

+1

"No, esa sería una muy mala razón para usar esto". ¿Podrían dar más detalles sobre este punto? ¿Qué podría usarse en su lugar para lograr eso? – coincoin

+0

@coincoin: ¿Para lograr exactamente qué? –

+1

'para evitar la posible gran sobrecarga de copiar un objeto complejo grande?' – coincoin

1

Las referencias de los miembros se suelen considerar 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.

+13

¿Puede proporcionar referencias de soporte * que generalmente se consideran malas *? –

1

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.

+0

pero el código en el OP no contiene una referencia a ninguna variable miembro. tiene una variable miembro que es una referencia. Entonces, grandes puntos, pero aparentemente sin relación. –

16

¿Hay algún nombre para describir este idioma?

No hay nombre para este uso, es simplemente conocido como "de referencia como miembro de la clase".

Supongo que es para evitar la posible gran sobrecarga de copiar un objeto grande y complejo?

Sí y también escenarios en los que desea asociar la duración de un objeto con otro.

¿Esta es una buena práctica en general? ¿Hay algún inconveniente en este enfoque?

Depende de su uso. Usar cualquier función de idioma es como "elegir caballos para cursos". Es importante tener en cuenta que cada característica de lenguaje (casi todos) existe porque es útil en algunos casos.
Hay algunos puntos importantes a tener en cuenta al utilizar referencias como miembros de la clase:

  • Usted necesita asegurarse de que el objeto se hace referencia está garantizada existiendo hasta existe el objeto de clase.
  • Debe inicializar el miembro en la lista de inicializadores de miembros del constructor. No puede tener inicialización diferida, que podría ser posible en caso de miembro del puntero.
  • El compilador no generará la asignación de copia operator=() y deberá proporcionarla usted mismo. Es engorroso determinar qué acción tomará su operador = en tal caso. Así que, básicamente, su clase se convierte en no asignable.
  • Las referencias no pueden ser NULL ni pueden 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 realmente le preocupe el alto uso de memoria debido al tamaño de miembro) basta con tener una instancia 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 utilizar un puntero inteligente en lugar de un puntero sin formato. Eso haría su vida mucho más fácil con punteros.

+0

"** Supongo que es para evitar la posible gran sobrecarga de copiar un objeto complejo grande? ** Sí" - explique por qué cree que este patrón tiene alguna relevancia para evitar la copia, ya que no puedo ver ninguno. si este no-'idiom 'de alguna manera guarda copias para alguien como un efecto secundario de sus propósitos reales, entonces su diseño original fue fatalmente defectuoso para empezar, y simplemente reemplazarlo en el lugar con este patrón es poco probable que haga lo que esperan . –

19

Se llama dependency injection via constructor injection: 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-corrección que escribiría:

using T = int; 

class A 
{ 
public: 
    A(const T &thing) : m_thing(thing) {} 
    // ... 

private: 
    const T &m_thing; 
}; 

sino un problema de esta clase es que acepta las 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 se cambia el constructor:

class A 
{ 
public: 
    A(const T *thing) : m_thing(*thing) { assert(thing); } 
    // ... 

private: 
    const T &m_thing; 
}; 

Es casi seguro que you won't have a pointer to a temporary.

Además, como el constructor toma un puntero, es más claro para los usuarios de A que deben prestar atención a la duración del objeto que pasan.


temas relacionados en alguna medida son:

Cuestiones relacionadas