¿Cuál es el caso de uso para usar la clase de amigo C ++?

8

Estoy tratando de entender C ++ amigo. ¿Cuándo es el buen caso de uso para usar amigo? Supongo que si queremos permitir que otra clase tenga acceso a los atributos de otras clases, ¿por qué no lo hacemos público o heredamos de esa clase?

Gracias por tu ayuda.

Joshua Partogi
fuente
44
Creo que esta pregunta definitivamente pertenece aquí, ya que es más un "¿por qué?" pregunta que un "¿cómo?" pregunta, y ya se han hecho algunos buenos puntos en las respuestas.
Larry Coleman

Respuestas:

14

Hacer que un miembro de la clase publicsignifique otorgar acceso a todos a ella, rompiendo así la encapsulación por completo.

La herencia de una clase a menudo no es deseable, si la clase amiga no está destinada a ser una subclase. Subclasificar solo para acceder a los elementos internos de una clase es un grave error de diseño. E incluso una subclase no puede ver los miembros privados de su clase base.

Un uso típico de friendes para operadores que no pueden ser miembros, como operadores de flujo, operator+etc. En estos casos, la clase real con la que están asociados no es (siempre) el primer parámetro de la función, por lo que la función no puede implementarse como un método miembro.

Otro ejemplo es implementar un iterador para una colección. El iterador (y solo el iterador) necesita ver las partes internas de su colección principal, sin embargo, no es una subclase de la colección.

Péter Török
fuente
A veces, esas funciones de amigo gratuitas se definen en línea en la clase.
Deduplicador
3

El ejemplo que he visto con mayor frecuencia para las clases de amigos que se usan en C ++ es en pruebas unitarias. Las pruebas unitarias generalmente quieren saber todo sobre sus partes internas, pero no son parte de usted, y no tiene sentido que intenten heredar de usted.

Si no está familiarizado con la escritura de pruebas unitarias, le sugiero que comience de inmediato.

btilly
fuente
66
Las clases de producción no deben saber nada sobre las clases de prueba unitaria. Y las pruebas no deberían necesitar ver las partes internas de la clase probada; Una clase bien diseñada puede ser probada a través de su API pública. De lo contrario, lo más probable es que no sea una clase bien diseñada (por ejemplo, intenta hacer demasiado, por lo que parte de su funcionalidad debería extraerse mejor en una clase separada).
Péter Török
44
@ peter-torok: Eso depende de lo que "bien diseñado" signifique para usted. Un caso común en el que quiero acceso privilegiado es inyectar simulacros para rastrear si las acciones que se suponía que sucederían ocurrieron. Exponer esas partes de la estructura del objeto en la API pública parece una violación importante de la encapsulación. Por lo tanto, a menudo quiero que el código de prueba de la unidad tenga acceso privilegiado.
btilly
1
@btilly: si desea inyectar un simulacro, puede usar un parámetro en el constructor, sin necesidad de acceder a las partes internas de la clase.
Giorgio
1
@ PéterTörök GTest no está de acuerdo contigo. Hay algunos casos en los que una prueba de amigo sería útil, por ejemplo, uno podría desear comparar la equivalencia de dos objetos de la misma clase sin exponer una operación de equivalencia en la API (quizás por cuestiones de seguridad).
VF1
1
@Giorgio Sí, puede hacer que la información que desea probar en la unidad sea pública. Pero ahora su API pública acaba de crecer y puede encerrarlo en decisiones de diseño que serían imprudentes en el futuro. Dicho de otra manera, es apropiado tener pruebas unitarias para los casos límite de su implementación actual. No siempre es apropiado exponer ese nivel de detalle a los consumidores de su clase.
btilly
0

Esta pregunta debería estar en stackoverflow. De todos modos, usa un amigo solo cuando desea compartir las partes privadas de su clase con otra clase (o clases) pero no con nadie más. Si los hace públicos, todos pueden ver sus partes privadas (juego de palabras ;-P). Hay dos restricciones importantes que imponen la privacidad: 1) debe especificar quién es su amigo. Nadie más puede ser un amigo. 2) no se puede heredar el comportamiento "amigable" en las subclases de la clase amigo

DPD
fuente
1
Pregunta del método socrático: ¿Y por qué es malo que todos vean sus partes privadas?
Larry Coleman
3
@Larry: depende de si se hace con buen gusto o de un valor de choque: D
Matt Ellen
@ Matt: ¿hay un buen uso de las variables públicas?
Larry Coleman
@Larry: (no eufemísticamente) al cambiar el estado del objeto, fuera del control del objeto, no hará que el objeto esté en un estado no válido. Creo que los eventos en C # cuentan a este respecto.
Matt Ellen
0

Un uso típico es otorgar acceso a funciones que forman parte de la interfaz de la clase, pero gracias a otras reglas realmente no puede ser parte de la clase propiamente dicha. Los insertadores / extractores para iostreams son un ejemplo clásico:

namespace whatever { 
    class something { 
        // ...    
        friend std::ostream &operator<<(std::ostream &os, something const &thing);
        friend std::istream &operator>>(std::istream &is, something &thing);
    };
}

Hacer que estos operadores sean miembros de la clase no funcionará. Una operación de E / S se parece a:

whatever::something thing;

std::cout << thing;

Para un operador implementado como una función miembro, esto se resolvería como:

std::cout.operator<<(thing);

Es decir, la función debería ser miembro de std::cout, no de something. Como no queremos modificar std::ostreamconstantemente, nuestra única opción razonable es sobrecargar al operador con una función libre en lugar de una función miembro. Eso nos deja con dos posibilidades: ignorar la encapsulación por completo y hacer que todo en la clase sea público, o mantenerlo privado, pero otorgar acceso a las pocas cosas que realmente lo necesitan.

Hacer que otra clase sea amiga es bastante menos común. En este caso, normalmente está creando algo en el orden de un módulo o subsistema: un conjunto de clases que funcionan juntas y tienen cierto grado de acceso especial entre sí que no se otorga al mundo en general.

Jerry Coffin
fuente
0

Hay un buen ejemplo de la necesidad de clases de amigos en esta solicitud de clases de amigos en C # , un lenguaje que no las tiene.

Cotizado al por mayor aquí:

Los modificadores de acceso en .NET fueron diseñados con la idea de que puede confiar en sus compañeros de trabajo para saber cómo funciona el código y, por lo tanto, no deslizarse e introducir un error.

Sin embargo, esta idea es errónea. ¡El desarrollo de software es tan complicado que un programador experimentado sabe que ni siquiera debe confiar en sí mismo! Por esta razón, los programadores experimentados prefieren escribir código que, por diseño, no se puede romper por un mal uso accidental. .NET proporciona las herramientas para hacer esto cuando solo participa una clase, pero en el momento en que un programador intenta crear un conjunto a prueba de balas de dos o tres clases interactivas, esto se rompe.

El enfoque destinado a .NET para esto es marcar a los miembros compartidos como "internos", lo que permite a las clases interactuar con las partes internas de los demás. Desafortunadamente, esto también permite que cualquier otra clase en la asamblea se meta con los internos, ya sea por accidente o intencionalmente.

Mads Torgersen, C # Language PM, incluso ha respondido a esta propuesta, afirmando que siente que la preocupación es realmente válida, pero no está seguro acerca de la implementación específica sugerida allí.

El C ++ friendes solo una de las formas de abordar el problema descrito.

enverpex
fuente
0

Cuando quieres que tus clases tengan relaciones monógamas.

Por ejemplo, cuando no desea que clases desconocidas accedan a sus elementos privados, pero sus socios sí necesitan acceso.

Danny Varod
fuente
Casos de uso similares a los de interno en C # y paquete en Java.
Danny Varod el
0

He encontrado que las clases de amigos en C ++ son útiles cuando en Java hubiera utilizado el acceso a paquetes. Es decir, tengo un montón de clases que están tan íntimamente relacionadas que están escritas y mantenidas como una unidad, en los mismos archivos de origen. Cualquiera que trabaje en uno de ellos realmente está trabajando en todos ellos. Los invariantes que mantienen mantienen juntos. Tiene sentido que vean las partes internas del otro para que puedan mantener su invariante compartido.

Lo que no tiene sentido es el acceso protegido. Si no es seguro exponer al público una parte de mis elementos internos, no es seguro exponerlo a lo que sea que aparezca más tarde en un proyecto completamente no relacionado y afirme ser mi subclase. Permitir que las subclases usen mi interfaz pública, como todos los demás. (A menos que sean amigos, pero en ese caso los escribí y los implementé en la misma fuente, por lo que apenas son de otro proyecto).

ganbustein
fuente
-1

Digamos que hay una función externa que requiere que realice operaciones en miembros privados o protegidos de dos clases diferentes dentro de su programa. ahora, para que esa función obtenga acceso y realmente use estos miembros de la clase, debe hacerlo amigo de ambas clases. Teniendo en cuenta que al hacer que sea un amigo de estas dos clases no lo hace miembro de las clases, solo gana el impedimento de referirse a los miembros privados o protegidos de estas clases. Puedo decir que necesitaría usar una función de amigo cuando quiera operar en objetos de dos clases diferentes. Aunque el uso excesivo de la función y clase amigo puede reducir el valor de la encapsulación.

Fiko V
fuente
2
esta publicación es bastante difícil de leer (muro de texto). ¿Te importaría editarlo en una mejor forma?
mosquito