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

He estado leyendo las preguntas frecuentes de C ++ y tenía curiosidad acerca de la statement de friend . Personalmente, nunca lo he usado, sin embargo, estoy interesado en explorar el idioma.

¿Cuál es un buen ejemplo de usar friend ?


Leyendo un poco más las preguntas frecuentes Me gusta la idea de sobrecargar al operador << >> y agregarlo como amigo de esas clases. Sin embargo, no estoy seguro de cómo esto no rompe la encapsulación. ¿Cuándo pueden estas excepciones mantenerse dentro del rigor que es OOP?

En primer lugar (IMO) no escucho a las personas que dicen que el friend no es útil. Es útil. En muchas situaciones, tendrá objetos con datos o funcionalidades que no están destinados a ser de acceso público. Esto es particularmente cierto en el caso de grandes bases de código con muchos autores que pueden estar superficialmente familiarizados con diferentes áreas.

Existen alternativas al especificador amigo, pero a menudo son engorrosas (clases de hormigón de nivel cpp / typedefs enmascarados) o no infalibles (comentarios o convenciones de nombre de función).

En la respuesta;

El especificador friend permite que la clase designada acceda a datos o funcionalidades protegidos dentro de la clase haciendo la statement 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 simple ejemplo más allá al considerar una clase más compleja, como una ventana. Es muy probable que una ventana tenga muchos elementos de funciones / datos que no deberían ser accesibles al público, pero que son necesarios para una clase relacionada, como un administrador de ventanas.

 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 ); }; 

En el trabajo, usamos amigos para probar código , de manera extensiva. Significa que podemos proporcionar una encapsulación y ocultación de información adecuadas 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.

Basta con decir que no usaría la palabra clave friend como un componente esencial de su diseño.

La palabra clave friend tiene varios buenos usos. 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::fa, b; std::cout << (a + b).getValue(); // valid } 

Clase base privada CRTP

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

 // possible policy used for flexible-class. template 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(this)->getSomething(); } }; // note, derived privately template class SomePolicy> struct FlexibleClass : private SomePolicy { // 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; void doStuff() { // calls doSomething of the policy this->doSomething(); } // will return useful information some_type getSomething(); }; 

Encontrarás un ejemplo no artificial para eso en esta respuesta. Otro código que usa eso está en esta respuesta. La base CRTP arroja su puntero para poder acceder a los campos de datos de la clase derivada usando data-member-pointers.

@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 su operator << proclamaría 'Soy un amigo de la clase foo .

friend reemplaza el uso de public , no el uso de private !

En realidad, las preguntas frecuentes de C ++ ya lo resuelven .

El ejemplo canónico es sobrecargar al operador <<. Otro uso común es permitir que un ayudante o clase de administrador acceda a su interior.

Aquí hay un par de pautas que escuché sobre los amigos de C ++. El último es particularmente memorable.

  • Tus amigos no son amigos de tu hijo.
  • Los amigos de su hijo no son sus amigos.
  • Solo los amigos pueden tocar tus partes privadas.

editar: Leyendo un poco más las preguntas frecuentes Me gusta la idea de sobrecargar al operador << >> y agregarlo 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 irrestricto 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; }; 

c1 obviamente no está encapsulado. Cualquiera puede leer y modificar x en él. No tenemos forma de aplicar ningún tipo de control de acceso.

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

c3 ? ¿Está eso menos encapsulado? ¿Permite el acceso irrestricto a x ? ¿Permite el acceso a funciones desconocidas?

No. Permite precisamente una función para acceder a los miembros privados de la clase. Al igual que c2 hizo. Y al igual que c2 , la única función que tiene acceso no es “alguna función aleatoria desconocida”, sino “la función enumerada 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 esto exactamente 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 la clase.

friend no rompe la encapsulación. Hace que algunos progtwigdores de Java se sientan incómodos, porque cuando dicen “OOP”, en realidad quieren decir “Java”. Cuando dicen “Encapsulación”, no significan “los miembros privados deben estar protegidos de accesos arbitrarios”, sino “una clase Java donde las únicas funciones que pueden acceder a miembros privados son miembros de la clase”, aunque esto es una completa tontería para varias razones

Primero, como ya se mostró, es demasiado restrictivo. No hay ninguna razón para que los métodos amigos no puedan hacer lo mismo.

En segundo lugar, no es lo suficientemente restrictivo. Considera una cuarta clase:

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

Esto, de acuerdo con la mencionada mentalidad de Java, está perfectamente encapsulado. Y, sin embargo, permite absolutamente cualquier persona para leer y modificar x . ¿Cómo es que aún tiene sentido? (sugerencia: no lo hace)

En pocas palabras: la encapsulación se trata de poder controlar qué funciones pueden acceder a los miembros privados. No se trata precisamente de dónde se encuentran las definiciones de estas funciones.

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 se hacen siempre juntas y en un orden constante, puede hacer que los métodos sean privados y tener una función de amigo para hacer cumplir 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 e imponer invariantes que atraviesan clases y objetos en funciones de amigo.

¿Usted controla los derechos de acceso para miembros y funciones utilizando el derecho Privado / Protegido / Público? entonces, asumiendo que la idea de cada uno de esos 3 niveles es clara, entonces debe quedar claro que nos falta algo …

La statement de un miembro / función como protegida, por ejemplo, es bastante genérica. Usted dice que esta función está fuera del scope de todos (a excepción de un niño heredado, por supuesto). Pero, ¿y las excepciones? cada sistema de seguridad te permite tener algún tipo de ‘lista blanca’ ¿no?

Así que amigo te permite tener la flexibilidad de tener un sólido aislamiento de objetos sólidos, pero permite crear un “vacío” para las cosas que crees que están 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 esa es 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 manera de verlo. La idea es controlar quién puede acceder a qué, tener o no una función de ajuste tiene poco que ver con eso.

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

Friend es útil cuando estás construyendo un contenedor y quieres implementar un iterador para esa clase.

La respuesta breve sería: use friend cuando realmente mejore la encapsulación. Mejorar la legibilidad y la usabilidad (los operadores << y >> son el ejemplo canónico) es también una buena razón.

En cuanto a los ejemplos de mejorar 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 buenas candidatas.

El creador de C ++ dice que no está aplicando 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 progtwig de conformidad estándar) otorgarse acceso a una clase sin modificar su fuente.

Está más que claro …

Tuvimos un problema interesante en una empresa en la que trabajé anteriormente, en la que utilizamos amigos para afectos decentes. Trabajé en nuestro departamento de framework creamos un sistema básico de nivel de motor sobre nuestro sistema operativo personalizado. Internamente tenemos una estructura de clase:

  Game / \ TwoPlayer SinglePlayer 

Todas estas clases fueron 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 de los niños. El problema era que Game tenía interfaces con 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 fueran 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.

Otro uso: amigo (+ herencia virtual) se puede utilizar para evitar derivar de una clase (también conocido como “hacer que una clase no pueda ser comprendida”) => 1 , 2

De 2 :

  class Fred; class FredBase { private: friend class Fred; FredBase() { } }; class Fred : private virtual FredBase { public: ... }; 

Para hacer TDD muchas veces he usado la palabra clave ‘friend’ en C ++.

¿Puede un amigo saber todo sobre mí?


Actualizado: Encontré esta valiosa respuesta sobre la palabra clave “amigo” del sitio de Bjarne Stroustrup .

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

Con respecto al operador << y el 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 es crear funciones de impresión pública (ostream &) y de lectura (istream &). Luego, escriba el operador << y el operador >> en términos de esas funciones. Esto brinda la ventaja adicional de permitirle hacer que esas funciones sean virtuales, lo que proporciona una serialización virtual.

Solo estoy usando la palabra clave friend para funciones protegidas de unittest. Algunos dirán que no debes probar la funcionalidad protegida. Yo, sin embargo, encuentro esta herramienta muy útil al agregar nuevas funcionalidades.

Sin embargo, no utilizo la palabra clave directamente en las declaraciones de clase, en su lugar utilizo un nifty template-hack para lograr esto:

 template 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 Friender: public ParentClass { public: Friender() {} virtual ~Friender() {} private: // MSVC != GCC #ifdef _MSC_VER friend ToFriend; #else friend class FriendIdentity::me; #endif }; /** * Gives access to protected variables/functions in unittests. * Usage: friendMe(this, someprotectedobject).someProtectedMethod(); */ template Friender & friendMe(Tester * me, ParentClass & instance) { return (Friender &)(instance); } 

Esto me permite hacer lo siguiente:

 friendMe(this, someClassInstance).someProtectedFunction(); 

Funciona en GCC y MSVC al menos.

Debes tener mucho cuidado con cuándo / dónde utilizas la palabra clave friend y, como tú, la he usado muy raramente. A continuación hay algunas notas sobre cómo usar friend y las alternativas.

Digamos que quiere 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 tratarse de MUCHOS accesadores, que es (un poco) más lento que el acceso variable directo, más difícil de leer y engorroso. El problema con el segundo enfoque es que rompe 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 palabra clave friend :

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

El método equal(Beer, Beer) ahora tiene acceso directo a los miembros privados de a y b (que pueden ser char *brand , float percentAlcohol , etc. Este es un ejemplo bastante artificial, antes aplicarías friend a un sobrecargado == operator , pero llegaremos a eso.

Algunas cosas a tener en cuenta:

  • Un friend NO 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 accesos y mutadores con amigos (¡también puede hacer que todo sea public !)
  • La amistad no es recíproca
  • La amistad no es transitiva
  • La amistad no es heredada
  • O bien, como explica la C ++ FAQ : “El hecho de que le conceda a usted el acceso a la amistad no garantiza automáticamente el acceso de sus hijos a mí, no otorga automáticamente el acceso de sus amigos y no me otorga acceso automáticamente a usted. ”

Solo uso friends cuando es mucho más difícil hacerlo de otra manera. Como otro ejemplo, muchas funciones matemáticas de vectores a menudo se crean como friends debido a la interoperabilidad de Mat2x2 , Mat3x3 , Mat4x4 , Vec2 , Vec3 , Vec4 , etc. Y es mucho más fácil ser amigos, en lugar de tener que usar accesadores en todas partes. Como se señaló, friend suele ser útil cuando se aplica al operador << (realmente útil para la depuración), >> y tal vez al == , 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 uso friend muy a menudo, pero de vez en cuando es justo lo que necesitas. ¡Espero que esto ayude!

El ejemplo de árbol es un buen ejemplo: tener un objeto implementado en unas pocas clases diferentes sin tener una relación de herencia.

Tal vez también podría necesitar tener un constructor protegido y forzar a las personas a usar su fábrica de “amigos”.

… Bien, francamente puedes vivir sin eso.

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

No, es solo una amistad de una sola dirección: `(

Una instancia específica donde uso friend es cuando creo clases de Singleton . La palabra clave friend me permite crear una función de acceso, que es más conciso que tener siempre 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; } 

Las funciones y clases de Friend 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 el 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 de encapsulación obvias. En primer lugar, ahora la clase o función de amigo tiene acceso completo a TODOS los miembros de la clase, incluso aquellos que no se corresponden con sus necesidades. En segundo lugar, las implementaciones de la clase y el amigo ahora están enmarañadas hasta el punto en que un cambio interno en la clase puede romper al amigo.

Si ves al amigo como una extensión de la clase, entonces esto no es un problema, lógicamente hablando. Pero, en ese caso, ¿por qué fue necesario hablar con el amigo en primer lugar?

Para lograr lo mismo que 'amigos' pretenden lograr, pero sin romper la encapsulación, uno 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 le hubiéramos declarado a B un amigo de A. El comstackdor optimizará las llamadas a funciones, 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.

En C ++, la palabra clave “amigo” es útil en la sobrecarga del operador y en la fabricación del puente.

1.) Palabra clave de Friend en la sobrecarga del operador:
Ejemplo de sobrecarga del operador es: supongamos que tenemos una clase “Punto” que tiene dos variables de flotación
“x” (para la coordenada x) e “y” (para la coordenada y). Ahora tenemos que sobrecargar "<<" (operador de extracción) de modo que si llamamos "cout << pointobj" , imprimirá la coordenada xey (donde pointobj es un objeto de clase Point). Para hacer esto, tenemos dos opciones:

    1. Cargue la función "operator << ()" en la clase "ostream".
    2. Cargue la función "operator << ()" en la clase "Point". 

Ahora la primera opción no es buena porque si necesitamos sobrecargar nuevamente este operador para alguna clase diferente, entonces tenemos que hacer un cambio de nuevo en la clase "ostream".
Es por eso que segundo es la mejor opción. Ahora el comstackdor puede llamar a la función "operator <<()" :

  1.Uso del objeto ostream cout.As: cout.operator << (Pointobj) (forma ostream class). 
2. Llamada sin un 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 la palabra clave "friend" porque podemos llamar a una función amiga sin un objeto. Ahora la statement de la función será As:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"

2.) Palabra clave de amigo en hacer puente:
Supongamos que tenemos que hacer una función en la que tenemos que acceder a un miembro privado de dos o más clases (generalmente denominado "puente"). Como hacer esto:
Para acceder a un miembro privado de una clase, debe ser miembro de esa clase. Now to access private member of other class every class should declare that function as a friend function. For example : Suppose there are two class A and B. A function "funcBridge()" want to access private member of both classes. Then both class should declare "funcBridge()" as:
friend return_type funcBridge(A &a_obj, B & b_obj);

I think this would help to understand friend keyword.

As the reference for friend declaration says:

The friend declaration appears in a class body and grants a function or another class access to private and protected members of the class where the friend declaration appears.

So just as a reminder, there are technical errors in some of the answers which say that friend can only visit protected members.

When implementing tree algorithms for class, the framework code the prof gave us had the tree class as a friend of the node class.

It doesn’t really do any good, other than let you access a member variable without using a setting function.

You may use friendship when different classes (not inheriting one from the other) are using private or protected members of the other class.

Typical use cases of friend functions are operations that are conducted between two different classes accessing private or protected members of both.

from http://www.cplusplus.com/doc/tutorial/inheritance/ .

You can see this example where non-member method accesses the private members of a class. This method has to be declared in this very class as a friend of the class.

 // friend functions #include  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; } 

Probably I missed something from the answers above but another important concept in encapsulation is hiding of implementation. Reducing access to private data members (the implementation details of a class) allows much easier modification of the code later. If a friend directly accesses the private data, any changes to the implementation data fields (private data), break the code accessing that data. Using access methods mostly eliminates this. Fairly important I would think.

This may not be an actual use case situation but may help to illustrate the use of friend between classes.

The ClubHouse

 class ClubHouse { public: friend class VIPMember; // VIP Members Have Full Access To Class private: unsigned nonMembers_; unsigned paidMembers_; unsigned vipMembers; std::vector 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 

The Members Class’s

 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{}; 

If you look at the relationship of these classes here; the ClubHouse holds a variety of different types of memberships and membership access. The Members are all derived from a super or base class since they all share an ID and an enumerated type that are common and outside classes can access their IDs and Types through access functions that are found in the base class.

However through this kind of hierarchy of the Members and its Derived classes and their relationship with the ClubHouse class the only one of the derived class’s that has “special privileges” is the VIPMember class. The base class and the other 2 derived classes can not access the ClubHouse’s joinVIPEvent() method, yet the VIP Member class has that privilege as if it has complete access to that event.

So with the VIPMember and the ClubHouse it is a two way street of access where the other Member Classes are limited.

You could adhere to the strictest and purest OOP principles and ensure that no data members for any class even have accessors so that all objects must be the only ones that can know about their data with the only way to act on them is through indirect messages , ie, methods.

But even C# has an internal visibility keyword and Java has its default package level accessibility for some things. C++ comes actually closer to the OOP ideal by minimizinbg the compromise of visibility into a class by specifying exactly which other class and only other classes could see into it.

I don’t really use C++ but if C# had friend s I would that instead of the assembly-global internal modifier, which I actually use a lot. It doesn’t really break incapsulation, because the unit of deployment in .NET is an assembly.

But then there’s the InternalsVisibleTo Attribute(otherAssembly) which acts like a cross-assembly friend mechanism. Microsoft uses this for visual designer assemblies.

Friends are also useful for callbacks. You could implement callbacks as static methods

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

where callback calls localCallback internally, and the clientData has your instance in it. In my opinion,

o…

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

What this allows is for the friend to be a defined purely in the cpp as a c-style function, and not clutter up the class.

Similarly, a pattern I’ve seen very often is to put all the really private members of a class into another class, which is declared in the header, defined in the cpp, and friended. This allows the coder to hide a lot of the complexity and internal working of the class from the user of the header.

In the header:

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

In the cpp,

 class MyFooPrivate { public: MyFoo *owner; // Your complexity here }; MyFoo::MyFoo() { this->_private->owner = this; } 

It becomes easier to hide things that the downstream needn’t see this way.

    Intereting Posts