He estado leyendo las preguntas frecuentes de C ++ y tenía curiosidad sobre la friend
declaración. Personalmente nunca lo he usado, sin embargo, estoy interesado en explorar el idioma.
¿Cuál es un buen ejemplo de uso friend
?
Leyendo las preguntas frecuentes un poco más Me gusta la idea de que el <<
>>
operador sobrecargue y agregue como amigo de esas clases. Sin embargo, no estoy seguro de cómo esto no rompe la encapsulación. ¿Cuándo pueden estas excepciones permanecer dentro de lo estricto que es POO?
c++
oop
encapsulation
friend
Peter Mortensen
fuente
fuente
Respuestas:
En primer lugar (IMO) no escuche a las personas que dicen que
friend
no es útil. Es útil. En muchas situaciones, tendrá objetos con datos o funcionalidades que no están destinados a estar disponibles públicamente. Esto es particularmente cierto en el caso de grandes bases de código con muchos autores que pueden estar familiarizados superficialmente con diferentes áreas.Existen alternativas al especificador amigo, pero a menudo son engorrosas (clases concretas de nivel cpp / tipos de letra enmascarados) o no infalibles (comentarios o convenciones de nombres de funciones).
Sobre la respuesta;
El
friend
especificador permite el acceso de la clase designada a los datos protegidos o la funcionalidad dentro de la clase que hace la declaración de amigo. Por ejemplo, en el siguiente código, cualquiera puede preguntarle a un niño su nombre, pero solo la madre y el niño pueden cambiar el nombre.Puede llevar este sencillo ejemplo más allá al considerar una clase más compleja, como una ventana. Es muy probable que una ventana tenga muchos elementos de función / datos que no deberían ser accesibles públicamente, pero que una clase relacionada como un WindowManager necesita.
fuente
friend
mejora la encapsulación.friend
otorga acceso selectivo a los miembros, al igual que loprotected
hace. Cualquier control detallado es mejor que otorgar acceso público. Otros lenguajes también definen mecanismos de acceso selectivo, considere C # 'sinternal
. La mayoría de las críticas negativas sobre el uso defriend
están relacionadas con un acoplamiento más estricto, que generalmente se considera algo malo. Sin embargo, en algunos casos, un acoplamiento más estricto es precisamente lo que desea yfriend
le brinda ese poder.friend
es en lugar de proporcionar un ejemplo motivador . El ejemplo Window / WindowManager es mejor que el ejemplo que se muestra, pero es demasiado vago. Esta respuesta tampoco aborda la parte de encapsulación de la pregunta.En el trabajo, usamos amigos para probar el código , ampliamente. Significa que podemos proporcionar la encapsulación adecuada y la ocultación de información para el código de la aplicación principal. Pero también podemos tener un código de prueba separado que use amigos para inspeccionar el estado interno y los datos para las pruebas.
Baste decir que no usaría la palabra clave amigo como un componente esencial de su diseño.
fuente
La
friend
palabra clave tiene varios usos buenos. Aquí están los dos usos inmediatamente visibles para mí:Definición de amigo
La definición de amigo permite definir una función en el ámbito de clase, pero la función no se definirá como una función miembro, sino como una función libre del espacio de nombres adjunto, y no será visible normalmente, excepto para la búsqueda dependiente de argumentos. Eso lo hace especialmente útil para la sobrecarga del operador:
Clase base privada de CRTP
A veces, encuentra la necesidad de que una política necesite acceso a la clase derivada:
Encontrará un ejemplo no inventado para eso en esta respuesta. Otro código que usa eso está en esta respuesta. La base CRTP convierte su puntero este, para poder acceder a los campos de datos de la clase derivada utilizando punteros de miembros de datos.
fuente
P<C>
entemplate<template<typename> class P> class C : P<C> {};
que dice "Uso de plantilla de clase C requiere argumentos de plantilla". ¿Has tenido los mismos problemas o quizás conoces una solución?FlexibleClass
insideFlexibleClass
debe referirse implícitamente a su propio tipo.@roo : la encapsulación no se rompe aquí porque la clase misma dicta quién puede acceder a sus miembros privados. La encapsulación solo se rompería si esto pudiera ser causado desde fuera de la clase, por ejemplo, si usted
operator <<
proclamara "Soy un amigo de la clasefoo
".friend
reemplaza el uso depublic
, no el uso deprivate
!En realidad, las preguntas frecuentes de C ++ ya responden esto .
fuente
friend
no es la excepción. La única observación real aquí es que C ++ asegura la encapsulación solo en tiempo de compilación. Y no necesitas más palabras para decirlo. El resto son bollocks. Entonces, en resumen: no vale la pena mencionar esta sección de la FQA.El ejemplo canónico es sobrecargar el operador <<. Otro uso común es permitir un acceso de clase auxiliar o de administrador a sus componentes internos.
Aquí hay un par de pautas que escuché sobre los amigos de C ++. El último es particularmente memorable.
fuente
friend
, supongo.¿Cómo rompería la encapsulación?
Rompe la encapsulación cuando permite el acceso sin restricciones a un miembro de datos. Considere las siguientes clases:
c1
es , obviamente, no encapsulada. Cualquiera puede leer y modificarx
en él. No tenemos forma de imponer ningún tipo de control de acceso.c2
obviamente está encapsulado. No hay acceso público ax
. Todo lo que puede hacer es llamar a lafoo
función, que realiza una operación significativa en la clase .c3
? ¿Eso está menos encapsulado? ¿Permite el acceso sin restriccionesx
? ¿Permite el acceso a funciones desconocidas?No. Permite precisamente una función para acceder a los miembros privados de la clase. Justo como lo
c2
hizo. Y al igual quec2
, la función que tiene el acceso no es "un poco, función desconocida al azar", pero "la función que aparece en la definición de la clase". Al igual quec2
, podemos ver, simplemente mirando las definiciones de clase, una lista completa de quién tiene acceso.Entonces, ¿cómo es exactamente esto menos encapsulado? La misma cantidad de código tiene acceso a los miembros privados de la clase. Y todos los que tienen acceso se enumeran en la definición de clase.
friend
No rompe la encapsulación. Hace que algunos programadores de personas Java se sientan incómodos, porque cuando dicen "OOP", en realidad quieren decir "Java". Cuando dicen "encapsulación", no quieren decir "miembros privados deben ser protegidos de accesos arbitrarias", sino "una clase Java, donde las únicas funciones capaces de miembros privados de acceso, son miembros de la clase", aunque esto no tiene ningún sentido para varias razones .Primero, como ya se mostró, es demasiado restrictivo. No hay ninguna razón por la cual no se permita que los métodos amigos hagan lo mismo.
En segundo lugar, no es restrictiva suficiente . Considere una cuarta clase:
Esto, de acuerdo con la mentalidad de Java mencionada anteriormente, está perfectamente encapsulado. Y sin embargo, permite que absolutamente cualquier persona lea y modifique x . ¿Cómo es que aún tiene sentido? (pista: no lo hace)
En pocas palabras: la encapsulación se trata de poder controlar qué funciones pueden acceder a miembros privados. Es no sobre la forma precisa en que se encuentran las definiciones de estas funciones.
fuente
Otra versión común del ejemplo de Andrew, el temido código-pareado
En lugar de preocuparse si ambas líneas siempre se hacen juntas y en un orden coherente, puede hacer que los métodos sean privados y tener una función amiga para garantizar la coherencia:
En otras palabras, puede mantener las interfaces públicas más pequeñas y aplicar invariantes que atraviesan las clases y los objetos en las funciones de amigo.
fuente
addChild
función miembro también establezca el elemento primario?setParent
un amigo, ya que no desea permitir que los clientes cambien el padre, ya que lo administrará en la categoríaaddChild
/removeChild
de funciones.¿Controla los derechos de acceso de los miembros y las funciones mediante el derecho privado / protegido / público? entonces, asumiendo que la idea de todos y cada uno de esos 3 niveles es clara, entonces debería estar claro que nos falta algo ...
La declaración de un miembro / función como protegido, por ejemplo, es bastante genérica. Está diciendo que esta función está fuera del alcance de todos (a excepción de un hijo heredado, por supuesto). ¿Pero qué hay de las excepciones? cada sistema de seguridad le permite tener algún tipo de 'lista blanca ", ¿verdad?
Así que amigo le permite tener la flexibilidad de tener un aislamiento de objetos sólidos como una roca, pero permite que se cree un "vacío" para las cosas que usted considera justificadas.
Supongo que la gente dice que no es necesario porque siempre hay un diseño que funcionará sin él. Creo que es similar a la discusión de las variables globales: nunca debes usarlas, siempre hay una manera de prescindir de ellas ... pero en realidad, ves casos en los que eso termina siendo la forma (casi) más elegante. .. Creo que este es el mismo caso con amigos.
bueno, esa no es exactamente la forma de verlo. La idea es controlar quién puede acceder a qué, tener o no una función de configuración tiene poco que ver con eso.
fuente
friend
una escapatoria? Permite que los métodos enumerados en la clase accedan a sus miembros privados. Todavía no permite que el código arbitrario acceda a ellos. Como tal, no es diferente de una función de miembro público.Encontré un lugar útil para usar el acceso amigo: Unittest de funciones privadas.
fuente
Friend es útil cuando está creando un contenedor y desea implementar un iterador para esa clase.
fuente
Tuvimos un problema interesante en una empresa en la que trabajé anteriormente, donde usábamos amigos con un efecto decente. Trabajé en nuestro departamento de framework, creamos un sistema básico de nivel de motor sobre nuestro sistema operativo personalizado. Internamente teníamos una estructura de clase:
Todas estas clases eran parte del marco y mantenidas por nuestro equipo. Los juegos producidos por la compañía se construyeron sobre este marco derivado de uno de los juegos para niños. El problema era que Game tenía interfaces para varias cosas a las que SinglePlayer y TwoPlayer necesitaban acceso, pero que no queríamos exponer fuera de las clases de framework. La solución fue hacer que esas interfaces sean privadas y permitir que TwoPlayer y SinglePlayer accedan a ellas a través de la amistad.
A decir verdad, todo este problema podría haberse resuelto mediante una mejor implementación de nuestro sistema, pero estábamos encerrados en lo que teníamos.
fuente
La respuesta corta sería: use friend cuando realmente mejore la encapsulación. Mejorar la legibilidad y la usabilidad (los operadores << y >> son el ejemplo canónico) también es una buena razón.
En cuanto a los ejemplos de mejora de la encapsulación, las clases específicamente diseñadas para trabajar con las partes internas de otras clases (las clases de prueba vienen a la mente) son buenos candidatos.
fuente
<<
y>>
generalmente son amigos, en lugar de miembros, porque convertirlos en miembros los haría incómodos de usar. Por supuesto, estoy hablando del caso en que esos operadores necesitan acceder a datos privados; de lo contrario, la amistad es inútil.operator<<
yoperator>>
los miembros de la clase de valor en lugar de los no miembros, o miembros dei|ostream
, no proporcionarían la sintaxis deseada, y estoy no sugerirlo. " Estoy hablando del caso en que esos operadores necesitan acceder a datos privados " No entiendo por qué los operadores de entrada / salida necesitarían acceder a miembros privados.El creador de C ++ dice que no está rompiendo ningún principio de encapsulación, y lo citaré:
Está más que claro ...
fuente
Otro uso: amigo (+ herencia virtual) se puede usar para evitar derivar de una clase (también conocido como "hacer que una clase sea subestimable") => 1 , 2
De 2 :
fuente
Para hacer TDD muchas veces he usado la palabra clave 'amigo' en C ++.
¿Puede un amigo saber todo sobre mí?
Actualizado: Encontré esta valiosa respuesta sobre la palabra clave "amigo" del sitio Bjarne Stroustrup .
fuente
Debe tener mucho cuidado con cuándo / dónde usa la
friend
palabra clave y, como usted, la he usado muy raramente. A continuación hay algunas notas sobre el usofriend
y las alternativas.Digamos que quieres comparar dos objetos para ver si son iguales. Usted podría:
El problema con la primera opción es que podría haber MUCHOS accesorios, que es (ligeramente) más lento que el acceso variable directo, más difícil de leer y engorroso. El problema con el segundo enfoque es que rompes completamente la encapsulación.
Lo que sería bueno es si pudiéramos definir una función externa que aún pudiera tener acceso a los miembros privados de una clase. Podemos hacer esto con la
friend
palabra clave:El método
equal(Beer, Beer)
tiene ahora acceso directoa
yb
's miembros privados (que puede serchar *brand
,float percentAlcohol
etc. Este es un ejemplo bastante artificiales, que sería más rápido aplicarfriend
a un sobrecargado== operator
, pero vamos a llegar a eso.Algunas cosas a tener en cuenta:
friend
NO es una función miembro de la clasepublic
!)Realmente solo lo uso
friends
cuando es mucho más difícil hacerlo de otra manera. Como otro ejemplo, las funciones matemáticas vectoriales muchos se crean a menudo comofriends
debido a la interoperabilidad deMat2x2
,Mat3x3
,Mat4x4
,Vec2
,Vec3
,Vec4
, etc, y sólo mucho más fácil ser amigos, en lugar de tener que utilizar métodos de acceso en todas partes. Como se señaló, afriend
menudo es útil cuando se aplica a<<
(realmente útil para la depuración),>>
y tal vez al==
operador, pero también se puede usar para algo como esto:Como digo, no lo uso con
friend
mucha frecuencia, pero de vez en cuando es justo lo que necesita. ¡Espero que esto ayude!fuente
Con respecto al operador << y al operador >> no hay una buena razón para hacer amigos a estos operadores. Es cierto que no deberían ser funciones miembro, pero tampoco necesitan ser amigos.
Lo mejor que puede hacer es crear funciones de impresión pública (ostream &) y lectura (istream &). Luego, escriba el operador << y el operador >> en términos de esas funciones. Esto brinda el beneficio adicional de permitirle hacer que esas funciones sean virtuales, lo que proporciona serialización virtual.
fuente
Solo estoy usando la palabra clave friend para probar las funciones protegidas. Algunos dirán que no debe probar la funcionalidad protegida. Sin embargo, encuentro esta herramienta muy útil cuando agrego nuevas funcionalidades.
Sin embargo, no uso la palabra clave directamente en las declaraciones de clase, sino que utilizo un ingenioso hack de plantilla para lograr esto:
Esto me permite hacer lo siguiente:
Funciona en GCC y MSVC al menos.
fuente
En C ++, la palabra clave "amigo" es útil en la sobrecarga del operador y Making Bridge.
Ahora la primera opción no es buena porque si necesitamos sobrecargar nuevamente este operador para alguna clase diferente, entonces tenemos que hacer nuevamente el cambio en la clase "ostream".1.) Palabra clave de amigo en la sobrecarga del operador:
Ejemplo de sobrecarga del operador es: Digamos que tenemos una clase "Punto" que tiene dos variables flotantes
"x" (para la coordenada x) y "y" (para la coordenada y). Ahora tenemos que sobrecargar
"<<"
(operador de extracción) de modo que si llamamos"cout << pointobj"
, imprimirá las coordenadas x e y (donde pointobj es un objeto de clase Point). Para hacer esto tenemos dos opciones:Es por eso que la segunda es la mejor opción. Ahora el compilador puede llamar a la
"operator <<()"
función:Porque hemos implementado la sobrecarga en la clase Point. Entonces, para llamar a esta función sin un objeto, debemos agregar una
"friend"
palabra clave porque podemos llamar a una función amiga sin un objeto. Ahora la declaración de la función será As:"friend ostream &operator<<(ostream &cout, Point &pointobj);"
2.) Palabra clave de amigo al hacer bridge:
Supongamos que tenemos que hacer una función en la que tengamos que acceder al miembro privado de dos o más clases (generalmente denominado "bridge"). Cómo hacer esto:
Para acceder al miembro privado de una clase, debe ser miembro de esa clase. Ahora, para acceder al miembro privado de otra clase, cada clase debe declarar esa función como una función amiga. Por ejemplo: supongamos que hay dos clases A y B. Una función
"funcBridge()"
desea acceder al miembro privado de ambas clases. Entonces ambas clases deben declarar"funcBridge()"
como:friend return_type funcBridge(A &a_obj, B & b_obj);
Creo que esto ayudaría a comprender la palabra clave amigo.
fuente
Como dice la referencia para la declaración de amigo :
Así que solo como recordatorio, hay errores técnicos en algunas de las respuestas que dicen que
friend
solo pueden visitar miembros protegidos .fuente
El ejemplo del árbol es un buen ejemplo: tener un objeto implementado en algunas clases diferentes sin tener una relación de herencia.
Tal vez también podría necesitarlo para tener un constructor protegido y obligar a las personas a usar su fábrica de "amigos".
... Ok, francamente puedes vivir sin él.
fuente
No, es solo una amistad unidireccional: `(
fuente
Una instancia específica donde uso
friend
es cuando creo clases Singleton . Lafriend
palabra clave me permite crear una función de acceso, que es más concisa que siempre tener un método "GetInstance ()" en la clase.fuente
friend
no no necesita una "justificación" en particular, al agregar una función miembro no lo hace.Las funciones y clases de amigo proporcionan acceso directo a miembros de clase privados y protegidos para evitar romper la encapsulación en el caso general. La mayor parte del uso es con ostream: nos gustaría poder escribir:
Sin embargo, esto puede requerir acceso a los datos privados de Point, por lo que definimos el operador sobrecargado
Sin embargo, hay implicaciones obvias de encapsulación. Primero, ahora la clase o función de amigo tiene acceso completo a TODOS los miembros de la clase, incluso aquellos que no corresponden a sus necesidades. En segundo lugar, las implementaciones de la clase y el amigo ahora están enredadas hasta el punto en que un cambio interno en la clase puede romper al amigo.
Si ve al amigo como una extensión de la clase, entonces esto no es un problema, lógicamente hablando. Pero, en ese caso, ¿por qué era necesario hablar con el amigo en primer lugar?
Para lograr lo mismo que pretenden lograr los 'amigos', pero sin romper la encapsulación, se puede hacer esto:
La encapsulación no está rota, la clase B no tiene acceso a la implementación interna en A, sin embargo, el resultado es el mismo que si hubiéramos declarado a B amigo de A. El compilador optimizará las llamadas de función, por lo que esto dará como resultado el mismo instrucciones como acceso directo.
Creo que usar 'amigo' es simplemente un atajo con un beneficio discutible, pero un costo definido.
fuente
Esto puede no ser una situación de caso de uso real, pero puede ayudar a ilustrar el uso de amigo entre clases.
The ClubHouse
La clase de miembros
Comodidades
Si nos fijamos en la relación de estas clases aquí; ClubHouse posee una variedad de diferentes tipos de membresías y acceso a membresías. Todos los miembros se derivan de una clase super o base, ya que todos comparten un ID y un tipo enumerado que son comunes y las clases externas pueden acceder a sus ID y tipos a través de funciones de acceso que se encuentran en la clase base.
Sin embargo, a través de este tipo de jerarquía de los Miembros y sus clases Derivadas y su relación con la clase ClubHouse, la única de las clases derivadas que tiene "privilegios especiales" es la clase VIPMember. La clase base y las otras 2 clases derivadas no pueden acceder al método joinVIPEvent () de ClubHouse, pero la clase Miembro VIP tiene ese privilegio como si tuviera acceso completo a ese evento.
Entonces, con VIPMember y ClubHouse es una calle de acceso de dos vías donde las otras clases de miembros son limitadas.
fuente
Al implementar algoritmos de árbol para la clase, el código marco que nos dio el profesor tenía a la clase de árbol como amiga de la clase de nodo.
Realmente no sirve de nada, aparte de permitirle acceder a una variable miembro sin usar una función de configuración.
fuente
Puede usar la amistad cuando diferentes clases (sin heredar una de la otra) están usando miembros privados o protegidos de la otra clase.
de http://www.cplusplus.com/doc/tutorial/inheritance/ .
Puede ver este ejemplo donde el método no miembro accede a los miembros privados de una clase. Este método debe declararse en esta misma clase como amigo de la clase.
fuente
Probablemente me perdí algo de las respuestas anteriores, pero otro concepto importante en la encapsulación es ocultar la implementación. Reducir el acceso a los miembros de datos privados (los detalles de implementación de una clase) permite una modificación mucho más fácil del código más adelante. Si un amigo accede directamente a los datos privados, cualquier cambio en los campos de datos de implementación (datos privados), rompa el código que accede a esos datos. El uso de métodos de acceso elimina principalmente esto. Bastante importante, creo.
fuente
Usted podría adherirse a los principios más estricta y pura programación orientada a objetos y asegurarse de que no hay miembros de datos de cualquier clase, incluso tienen descriptores de acceso de modo que todos los objetos deben ser los únicos que se conocen acerca de sus datos con la única manera de actuar sobre ellos es a través indirectos mensajes , es decir, métodos.
Pero incluso C # tiene una palabra clave de visibilidad interna y Java tiene su accesibilidad de nivel de paquete predeterminada para algunas cosas. C ++ se acerca realmente al ideal de OOP al minimizar el compromiso de visibilidad en una clase al especificar exactamente qué otra clase y solo otras clases podrían ver en ella.
Realmente no uso C ++, pero si C # tuviera amigos , lo haría en lugar del modificador interno global de ensamblaje , que en realidad uso mucho. Realmente no rompe la encapsulación, porque la unidad de implementación en .NET es un ensamblado.
Pero luego está el InternalsVisibleTo Attribute (otherAssembly) que actúa como un mecanismo amigo de ensamblaje cruzado . Microsoft usa esto para ensamblajes de diseñadores visuales .
fuente
Los amigos también son útiles para las devoluciones de llamada. Puede implementar devoluciones de llamada como métodos estáticos
donde
callback
llamalocalCallback
internamente yclientData
tiene su instancia en él. En mi opinión,o...
Lo que esto permite es que el amigo se defina puramente en el cpp como una función de estilo c, y no desordenar la clase.
Del mismo modo, un patrón que he visto muy a menudo es colocar a todos los miembros realmente privados de una clase en otra clase, que se declara en el encabezado, se define en el cpp y se hace amigo. Esto permite que el codificador oculte gran parte de la complejidad y el funcionamiento interno de la clase al usuario del encabezado.
En el encabezado:
En el cpp,
Se hace más fácil ocultar cosas que el río abajo no necesita ver de esta manera.
fuente