¿Cuándo deberías usar 'amigo' en C ++?

354

He estado leyendo las preguntas frecuentes de C ++ y tenía curiosidad sobre la frienddeclaració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?

Peter Mortensen
fuente
55
Si bien estoy de acuerdo con la respuesta de que una clase de amigos no es necesariamente una mala cosa, tiendo a tratarla como un código pequeño. A menudo, aunque no siempre, indica que la jerarquía de clases necesita ser reconsiderada.
Mawg dice que reinstalar a Monica el
1
Usaría una clase de amigo donde ya hay un acoplamiento estrecho. Para eso está hecho. Por ejemplo, una tabla de base de datos y sus índices están estrechamente acoplados. Cuando una tabla cambia, todos sus índices deben actualizarse. Entonces, la clase DBIndex declararía a DBTable como un amigo para que DBTable pueda acceder directamente a las partes internas del índice. Pero no habría una interfaz pública para DBIndex; ni siquiera tiene sentido leer un índice.
shawnhcorey
Los "puristas" de OOP con poca experiencia práctica argumentan que un amigo viola los principios de OOP, porque una clase debería ser el único responsable de su estado privado. Esto está bien, hasta que encuentre una situación común en la que dos clases necesitan mantener un estado privado compartido.
Kaalus

Respuestas:

335

En primer lugar (IMO) no escuche a las personas que dicen que friendno 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 friendespecificador 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.

class Child
{
//Mother class members can access the private parts of class Child.
friend class Mother;

public:

  string name( void );

protected:

  void setName( string newName );
};
Andrew Grant
fuente
114
Como nota adicional, las preguntas frecuentes de C ++ mencionan que friend mejora la encapsulación. friendotorga acceso selectivo a los miembros, al igual que lo protectedhace. Cualquier control detallado es mejor que otorgar acceso público. Otros lenguajes también definen mecanismos de acceso selectivo, considere C # 's internal. La mayoría de las críticas negativas sobre el uso de friendestá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 y friendle brinda ese poder.
André Caron el
55
¿Podría decir más sobre (clases concretas de nivel cpp) y (typedefs enmascarados), Andrew ?
OmarOthman el
18
Esta respuesta parece estar más centrada en explicar lo que friendes 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.
bames53
44
Entonces, ¿efectivamente existe 'amigo' porque C ++ no tiene noción de un paquete en el que todos los miembros puedan compartir detalles de implementación? Me interesaría mucho un ejemplo del mundo real.
weberc2
1
Creo que el ejemplo Madre / Niño es desafortunado. Estos nombres son adecuados para instancias, pero no para clases. (El problema muestra si tenemos 2 madres y cada una tiene su propio hijo).
Jo So
162

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.

Daemin
fuente
Eso es exactamente para lo que lo uso. Eso o simplemente establecer las variables miembro en protegido. Es una pena que no funcione para C ++ / CLI :-(
Jon Cage
12
Personalmente, desalentaría esto. Por lo general, está probando una interfaz, es decir, un conjunto de entradas proporciona el conjunto esperado de salida (s). ¿Por qué necesita inspeccionar los datos internos?
Graeme
55
@Graeme: Porque un buen plan de prueba incluye pruebas de caja blanca y caja negra.
Ben Voigt
1
Tiendo a estar de acuerdo con @Graeme, como se explica perfectamente en esta respuesta .
Alexis Leclerc
2
@Graeme puede que no sean datos internos directamente. Es posible que sea un método que realiza una operación o tarea específica en esos datos donde ese método es privado para la clase y no debería ser accesible públicamente, mientras que algún otro objeto podría necesitar alimentar o inicializar el método protegido de esa clase con sus propios datos.
Francis Cugler
93

La friendpalabra 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:

namespace utils {
    class f {
    private:
        typedef int int_type;
        int_type value;

    public:
        // let's assume it doesn't only need .value, but some
        // internal stuff.
        friend f operator+(f const& a, f const& b) {
            // name resolution finds names in class-scope. 
            // int_type is visible here.
            return f(a.value + b.value);
        }

        int getValue() const { return value; }
    };
}

int main() {
    utils::f a, b;
    std::cout << (a + b).getValue(); // valid
}

Clase base privada de CRTP

A veces, encuentra la necesidad de que una política necesite acceso a la clase derivada:

// possible policy used for flexible-class.
template<typename Derived>
struct Policy {
    void doSomething() {
        // casting this to Derived* requires us to see that we are a 
        // base-class of Derived.
        some_type const& t = static_cast<Derived*>(this)->getSomething();
    }
};

// note, derived privately
template<template<typename> class SomePolicy>
struct FlexibleClass : private SomePolicy<FlexibleClass> {
    // we derive privately, so the base-class wouldn't notice that, 
    // (even though it's the base itself!), so we need a friend declaration
    // to make the base a friend of us.
    friend class SomePolicy<FlexibleClass>;

    void doStuff() {
         // calls doSomething of the policy
         this->doSomething();
    }

    // will return useful information
    some_type getSomething();
};

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.

Johannes Schaub - litb
fuente
Hola, recibo un error de sintaxis (en xcode 4) cuando pruebo su CRTP. Xcode cree que estoy tratando de heredar una plantilla de clase. El error se produce al P<C>en template<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?
bennedich
@bennedich a primera vista, parece el tipo de error que obtendría con una compatibilidad insuficiente con la función C ++. Lo cual es bastante común entre los compiladores. El uso de FlexibleClassinside FlexibleClassdebe referirse implícitamente a su propio tipo.
Yakk - Adam Nevraumont
@bennedich: las reglas para el uso del nombre de una plantilla de clase dentro del cuerpo de la clase cambiaron con C ++ 11. Intente habilitar el modo C ++ 11 en su compilador.
Ben Voigt
En Visual Studio 2015 agregue este público: f () {}; f (int_type t): valor (t) {}; Para evitar este error del compilador: error C2440: '<function-style-cast>': no ​​se puede convertir de 'utils :: f :: int_type' a 'utils :: f' nota: ningún constructor podría tomar el tipo de fuente o el constructor la resolución de sobrecarga era ambigua
Damian
41

@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 clase foo".

friendreemplaza el uso de public, no el uso de private!

En realidad, las preguntas frecuentes de C ++ ya responden esto .

Konrad Rudolph
fuente
14
"! sustituye amigo utilizan de público, no privado de uso", lo segundo que
Waleed Eissa
26
@Assaf: sí, pero la FQA es, en su mayor parte, un montón de galimatías enojadas incoherentes sin ningún valor real. La parte en friendno 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.
Konrad Rudolph
12
La mayor parte de ese FQA es completamente blx :)
rama-jka toti
1
@ Konrad: "La única observación real aquí es que C ++ asegura la encapsulación solo en tiempo de compilación". ¿Algún idioma garantiza esto en tiempo de ejecución? Hasta donde sé, devolver referencias a miembros privados (y funciones, para lenguajes que permiten a los punteros a funciones o funciones como objetos de primera clase) está permitido en C #, Java, Python y muchos otros.
André Caron
@ André: la JVM y el CLR realmente pueden garantizar esto hasta donde yo sé. No sé si siempre se hace, pero supuestamente puedes proteger paquetes / ensambles contra tal intrusión (aunque nunca lo hice yo mismo).
Konrad Rudolph el
27

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.

  • Tus amigos no son amigos de tus hijos.
  • Los amigos de tus hijos no son tus amigos.
  • Solo los amigos pueden tocar tus partes privadas.
Mark Harrison
fuente
" El ejemplo canónico es sobrecargar el operador <<. " El canónico de no usar friend, supongo.
curioso
16

editar: leer las preguntas frecuentes un poco más Me gusta la idea de la sobrecarga del operador << >> y agregar como amigo de esas clases, sin embargo, no estoy seguro de cómo esto no rompe la encapsulación

¿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:

class c1 {
public:
  int x;
};

class c2 {
public:
  int foo();
private:
  int x;
};

class c3 {
  friend int foo();
private:
  int x;
};

c1es , obviamente, no encapsulada. Cualquiera puede leer y modificar xen él. No tenemos forma de imponer ningún tipo de control de acceso.

c2obviamente está encapsulado. No hay acceso público a x. Todo lo que puede hacer es llamar a la foofunción, que realiza una operación significativa en la clase .

c3? ¿Eso está menos encapsulado? ¿Permite el acceso sin restricciones x? ¿Permite el acceso a funciones desconocidas?

No. Permite precisamente una función para acceder a los miembros privados de la clase. Justo como lo c2hizo. Y al igual que c2, 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 que c2, 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.

friendNo 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:

class c4 {
public:
  int getx();
  void setx(int x);
private:
  int x;
};

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.

jalf
fuente
10

Otra versión común del ejemplo de Andrew, el temido código-pareado

parent.addChild(child);
child.setParent(parent);

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:

class Parent;

class Object {
private:
    void setParent(Parent&);

    friend void addChild(Parent& parent, Object& child);
};

class Parent : public Object {
private:
     void addChild(Object& child);

     friend void addChild(Parent& parent, Object& child);
};

void addChild(Parent& parent, Object& child) {
    if( &parent == &child ){ 
        wetPants(); 
    }
    parent.addChild(child);
    child.setParent(parent);
}

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.

maccullt
fuente
66
¿Por qué alguien necesitaría un amigo para eso? ¿Por qué no dejar que la addChildfunción miembro también establezca el elemento primario?
Nawaz
1
Un mejor ejemplo sería hacer setParentun amigo, ya que no desea permitir que los clientes cambien el padre, ya que lo administrará en la categoría addChild/ removeChildde funciones.
Ylisar
8

¿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.

Realmente no sirve de nada, aparte de permitirle acceder a una variable miembro sin usar una función de configuración

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.

csmba
fuente
2
¿Cómo es frienduna 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.
jalf
friend es lo más cercano que puede llegar a C # / acceso a nivel de paquete Java en C ++. @jalf: ¿qué pasa con las clases de amigos (como una clase de fábrica)?
Ogre Psalm33
1
@ Ogre: ¿Qué hay de ellos? Todavía le estás dando específicamente a esa clase y a nadie más acceso a las partes internas de la clase. No solo está dejando la puerta abierta para que un código desconocido arbitrario se joda con su clase.
jalf
8

Encontré un lugar útil para usar el acceso amigo: Unittest de funciones privadas.

VladimirS
fuente
Pero, ¿se puede usar una función pública para eso? ¿Cuál es la ventaja de usar el acceso de amigo?
Zheng Qu
@Maverobot ¿Podría dar más detalles sobre su pregunta?
VladimirS
5

Friend es útil cuando está creando un contenedor y desea implementar un iterador para esa clase.

rptony
fuente
4

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:

         Game
        /    \
 TwoPlayer  SinglePlayer

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.

Rayo
fuente
4

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.

Gorpik
fuente
" operadores << y >> son el ejemplo canónico " No. Ejemplos de contador canónicos más bien .
curioso
@curiousguy: los operadores <<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.
Gorpik
" Porque lo que los hace miembros les haría difícil de usar. " Obviamente, lo que hace operator<<y operator>>los miembros de la clase de valor en lugar de los no miembros, o miembros de i|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.
curioso
4

El creador de C ++ dice que no está rompiendo ningún principio de encapsulación, y lo citaré:

¿"Amigo" viola la encapsulación? No, no lo hace. "Amigo" es un mecanismo explícito para otorgar acceso, al igual que la membresía. No puede (en un programa conforme estándar) otorgarse acceso a una clase sin modificar su fuente.

Está más que claro ...

garzanti
fuente
@curiousguy: incluso en el caso de las plantillas, es cierto.
Nawaz
Se puede otorgar la amistad de @Nawaz Template, pero cualquiera puede hacer una nueva especialización parcial o explícita sin modificar la clase de concesión de amistad. Pero tenga cuidado con las violaciones de ODR cuando haga eso. Y no hagas eso de todos modos.
curioso
3

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 :

 class Fred;

 class FredBase {
 private:
   friend class Fred;
   FredBase() { }
 };

 class Fred : private virtual FredBase {
 public:
   ...
 }; 
Gian Paolo Ghilardi
fuente
3

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 .

"Amigo" es un mecanismo explícito para otorgar acceso, al igual que la membresía.

popopome
fuente
3

Debe tener mucho cuidado con cuándo / dónde usa la friendpalabra clave y, como usted, la he usado muy raramente. A continuación hay algunas notas sobre el uso friendy las alternativas.

Digamos que quieres comparar dos objetos para ver si son iguales. Usted podría:

  • Use métodos de acceso para hacer la comparación (verifique cada ivar y determine la igualdad).
  • O bien, puede acceder a todos los miembros directamente haciéndolos públicos.

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 friendpalabra clave:

class Beer {
public:
    friend bool equal(Beer a, Beer b);
private:
    // ...
};

El método equal(Beer, Beer)tiene ahora acceso directo ay b's miembros privados (que puede ser char *brand, float percentAlcoholetc. Este es un ejemplo bastante artificiales, que sería más rápido aplicar frienda un sobrecargado == operator, pero vamos a llegar a eso.

Algunas cosas a tener en cuenta:

  • A friendNO es una función miembro de la clase
  • Es una función ordinaria con acceso especial a los miembros privados de la clase.
  • No reemplace todos los accesores y mutadores con amigos (¡también puede hacer todo public!)
  • La amistad no es recíproca
  • La amistad no es transitiva.
  • La amistad no se hereda
  • O, como explican las preguntas frecuentes de C ++ : "El hecho de que le otorgue acceso de amistad a mí no le otorga a sus hijos acceso a mí automáticamente, no le otorga a sus amigos acceso a mí automáticamente, y no me otorga automáticamente acceso a usted ".

Realmente solo lo uso friendscuando es mucho más difícil hacerlo de otra manera. Como otro ejemplo, las funciones matemáticas vectoriales muchos se crean a menudo como friendsdebido a la interoperabilidad de Mat2x2, 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ó, a friendmenudo 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:

class Birds {
public:
    friend Birds operator +(Birds, Birds);
private:
    int numberInFlock;
};


Birds operator +(Birds b1, Birds b2) {
    Birds temp;
    temp.numberInFlock = b1.numberInFlock + b2.numberInFlock;
    return temp;
}

Como digo, no lo uso con friendmucha frecuencia, pero de vez en cuando es justo lo que necesita. ¡Espero que esto ayude!

Efímera
fuente
2

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
" Con respecto al operador << y al operador >> no hay una buena razón para hacer amigos a estos operadores " . Absolutamente correcto. " Esto brinda el beneficio adicional de permitirle hacer que esas funciones sean virtuales ". Si la clase en cuestión está destinada a derivación, sí. De lo contrario, ¿por qué molestarse?
curioso
Realmente no entiendo por qué esta respuesta fue rechazada dos veces, ¡y sin siquiera una explicación! Eso es rudo.
curioso
virtual agregaría un golpe de rendimiento que podría ser bastante grande en la serialización
Paul
2

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:

template<typename T>
class FriendIdentity {
public:
  typedef T me;
};

/**
 * A class to get access to protected stuff in unittests. Don't use
 * directly, use friendMe() instead.
 */
template<class ToFriend, typename ParentClass>
class Friender: public ParentClass
{
public:
  Friender() {}
  virtual ~Friender() {}
private:
// MSVC != GCC
#ifdef _MSC_VER
  friend ToFriend;
#else
  friend class FriendIdentity<ToFriend>::me;
#endif
};

/**
 * Gives access to protected variables/functions in unittests.
 * Usage: <code>friendMe(this, someprotectedobject).someProtectedMethod();</code>
 */
template<typename Tester, typename ParentClass>
Friender<Tester, ParentClass> & 
friendMe(Tester * me, ParentClass & instance)
{
    return (Friender<Tester, ParentClass> &)(instance);
}

Esto me permite hacer lo siguiente:

friendMe(this, someClassInstance).someProtectedFunction();

Funciona en GCC y MSVC al menos.

larsmoa
fuente
2

En C ++, la palabra clave "amigo" es útil en la sobrecarga del operador y Making Bridge.

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:

   1. Sobrecargue la función "operador << ()" en la clase "ostream".
   2. Sobrecargue la función "operador << ()" en la clase "Punto".
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".
Es por eso que la segunda es la mejor opción. Ahora el compilador puede llamar a la "operator <<()"función:

   1.Utilizando el objeto ostream cout. Como: cout.operator << (Pointobj) (forma de la clase ostream). 
2. Llamada sin objeto. Como: operador << (cout, Pointobj) (de la clase Point).

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.

revs jadnap
fuente
2

Como dice la referencia para la declaración de amigo :

La declaración de amigo aparece en un cuerpo de clase y otorga una función u otra clase de acceso a miembros privados y protegidos de la clase donde aparece la declaración de amigo.

Así que solo como recordatorio, hay errores técnicos en algunas de las respuestas que dicen que friendsolo pueden visitar miembros protegidos .

lixunhuan
fuente
1

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.

fulmicoton
fuente
1

Para hacer TDD muchas veces he usado la palabra clave 'amigo' en C ++.
¿Puede un amigo saber todo sobre mí?

No, es solo una amistad unidireccional: `(

roo
fuente
1

Una instancia específica donde uso friendes cuando creo clases Singleton . La friendpalabra clave me permite crear una función de acceso, que es más concisa que siempre tener un método "GetInstance ()" en la clase.

/////////////////////////
// Header file
class MySingleton
{
private:
    // Private c-tor for Singleton pattern
    MySingleton() {}

    friend MySingleton& GetMySingleton();
}

// Accessor function - less verbose than having a "GetInstance()"
//   static function on the class
MySingleton& GetMySingleton();


/////////////////////////
// Implementation file
MySingleton& GetMySingleton()
{
    static MySingleton theInstance;
    return theInstance;
}
Matt Dillard
fuente
Esto puede ser una cuestión de gustos, pero no creo que guardar unas pocas teclas justifique el uso de un amigo aquí. GetMySingleton () debería ser un método estático de la clase.
Gorpik
El c-tor privado no permitiría una función que no sea amiga para crear una instancia de MySingleton, por lo que aquí se necesita la palabra clave amigo.
JBRWilkinson
@Gorpik " Esto puede ser una cuestión de gustos, pero no creo que guardar unas pocas teclas justifique el uso de un amigo aquí ". De todos modos, friendno no necesita una "justificación" en particular, al agregar una función miembro no lo hace.
curioso
Los singletons se consideran una mala práctica de todos modos (Google "singleton nocivo" y obtendrá muchos resultados como este . No creo que usar una característica para implementar un antipatrón pueda considerarse un buen uso de esa característica.
weberc2
1

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:

Point p;
cout << p;

Sin embargo, esto puede requerir acceso a los datos privados de Point, por lo que definimos el operador sobrecargado

friend ostream& operator<<(ostream& output, const Point& p);

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:

class A
{
public:
    void need_your_data(B & myBuddy)
    {
        myBuddy.take_this_name(name_);
    }
private:
    string name_;
};

class B
{
public:
    void print_buddy_name(A & myBuddy)
    {
        myBuddy.need_your_data(*this);
    }
    void take_this_name(const string & name)
    {
        cout << name;
    }
}; 

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.

Lubo Antonov
fuente
1

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

class ClubHouse {
public:
    friend class VIPMember; // VIP Members Have Full Access To Class
private:
    unsigned nonMembers_;
    unsigned paidMembers_;
    unsigned vipMembers;

    std::vector<Member> members_;
public:
    ClubHouse() : nonMembers_(0), paidMembers_(0), vipMembers(0) {}

    addMember( const Member& member ) { // ...code }   
    void updateMembership( unsigned memberID, Member::MembershipType type ) { // ...code }
    Amenity getAmenity( unsigned memberID ) { // ...code }

protected:
    void joinVIPEvent( unsigned memberID ) { // ...code }

}; // ClubHouse

La clase de miembros

class Member {
public:
    enum MemberShipType {
        NON_MEMBER_PAID_EVENT,   // Single Event Paid (At Door)
        PAID_MEMBERSHIP,         // Monthly - Yearly Subscription
        VIP_MEMBERSHIP,          // Highest Possible Membership
    }; // MemberShipType

protected:
    MemberShipType type_;
    unsigned id_;
    Amenity amenity_;
public:
    Member( unsigned id, MemberShipType type ) : id_(id), type_(type) {}
    virtual ~Member(){}
    unsigned getId() const { return id_; }
    MemberShipType getType() const { return type_; }
    virtual void getAmenityFromClubHouse() = 0       
};

class NonMember : public Member {
public:
   explicit NonMember( unsigned id ) : Member( id, MemberShipType::NON_MEMBER_PAID_EVENT ) {}   

   void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class PaidMember : public Member {
public:
    explicit PaidMember( unsigned id ) : Member( id, MemberShipType::PAID_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class VIPMember : public Member {
public:
    friend class ClubHouse;
public:
    explicit VIPMember( unsigned id ) : Member( id, MemberShipType::VIP_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }

    void attendVIPEvent() {
        ClubHouse::joinVIPEvent( this->id );
    }
};

Comodidades

class Amenity{};

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.

Francis Cugler
fuente
0

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.

Ryan Fox
fuente
0

Puede usar la amistad cuando diferentes clases (sin heredar una de la otra) están usando miembros privados o protegidos de la otra clase.

Los casos de uso típicos de las funciones de amigo son operaciones que se realizan entre dos clases diferentes que acceden a miembros privados o protegidos de ambos.

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.

// friend functions
#include <iostream>
using namespace std;

class Rectangle {
    int width, height;
  public:
    Rectangle() {}
    Rectangle (int x, int y) : width(x), height(y) {}
    int area() {return width * height;}
    friend Rectangle duplicate (const Rectangle&);
};

Rectangle duplicate (const Rectangle& param)
{
  Rectangle res;
  res.width = param.width*2;
  res.height = param.height*2;
  return res;
}

int main () {
  Rectangle foo;
  Rectangle bar (2,3);
  foo = duplicate (bar);
  cout << foo.area() << '\n';
  return 0;
}
Kiriloff
fuente
0

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.

Petercasey
fuente
-1

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 .

Mark Cidade
fuente
-1

Los amigos también son útiles para las devoluciones de llamada. Puede implementar devoluciones de llamada como métodos estáticos

class MyFoo
{
private:
    static void callback(void * data, void * clientData);
    void localCallback();
    ...
};

donde callbackllama localCallbackinternamente y clientDatatiene su instancia en él. En mi opinión,

o...

class MyFoo
{
    friend void callback(void * data, void * callData);
    void localCallback();
}

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:

class MyFooPrivate;
class MyFoo
{
    friend class MyFooPrivate;
public:
    MyFoo();
    // Public stuff
private:
    MyFooPrivate _private;
    // Other private members as needed
};

En el cpp,

class MyFooPrivate
{
public:
   MyFoo *owner;
   // Your complexity here
};

MyFoo::MyFoo()
{
    this->_private->owner = this;
}

Se hace más fácil ocultar cosas que el río abajo no necesita ver de esta manera.

shash
fuente
1
¿No serían las interfaces una forma más limpia de lograr esto? ¿Qué puede evitar que alguien busque MyFooPrivate.h?
JBRWilkinson
1
Bueno, si estás usando lo privado y lo público para guardar secretos, serás derrotado fácilmente. Por "ocultar", quiero decir, el usuario de MyFoo realmente no necesita ver a los miembros privados. Además de esto, es útil mantener la compatibilidad ABI. Si convierte _private en un puntero, la implementación privada puede cambiar todo lo que desee, sin tocar la interfaz pública, manteniendo así la compatibilidad ABI.
shash
Te refieres al lenguaje PIMPL; el propósito para el cual no es una encapsulación adicional como parece estar diciendo, sino mover los detalles de implementación fuera del encabezado para que cambiar los detalles de implementación no obligue a una recompilación del código del cliente. Además, no es necesario usar friend para implementar este modismo.
weberc2
Bueno, sí. Su objetivo principal es mover los detalles de implementación. El amigo allí es útil para manejar miembros privados dentro de la clase pública desde el privado o al revés.
shash