Articulo de referencia

Clase de amigos

En C++, una clase amiga puede acceder a los miembros privados y protegidos de la clase en la que se declara como amiga. [ 1 ] Un uso importante de una clase amiga es que una par...

En C++, una clase amiga puede acceder a los miembros privados y protegidos de la clase en la que se declara como amiga. [ 1 ] Un uso importante de una clase amiga es que una parte de una estructura de datos , representada por una clase, proporcione acceso a la clase principal que representa dicha estructura. El mecanismo de clases amigas permite extender el almacenamiento y el acceso a las partes, manteniendo al mismo tiempo la encapsulación adecuada desde la perspectiva de los usuarios de la estructura de datos.

De forma similar a una clase amiga, una función amiga es una función a la que se le otorga acceso a los miembros privados y protegidos de la clase en la que se declara como amiga.

Desde C++26 , C++ admite "amigos variádicos" (es decir, clases amigas que provienen de plantillas variádicas ). [ 2 ]

Ejemplo

El siguiente ejemplo demuestra el uso de una clase amiga para una estructura de datos de grafo , donde el grafo está representado por la clase principal Graph, y los vértices del grafo están representados por la clase Vertex .

importar std ;plantilla < typename T > usando SharedPtr = std :: shared_ptr < T > ; usando String = std :: string ; plantilla < typename T > usando UniquePtr = std :: unique_ptr < T > ; plantilla < typename T > usando HashSet = std :: unordered_set < T > ; // especializar por defecto los parámetros de plantilla restantesclase Vértice { privado : // Vértice otorga derechos de acceso a Grafo. amigo clase Grafo ;HashSet < SharedPtr < Vertex >> edges ; String name ; using EdgesIterator = decltype ( edges . cbegin ()); public : explicit Vertex ( const String & name ) : name { std :: move ( name )} {}EdgesIterator begin () const { return edges . cbegin (); }EdgesIterator end () const { return edges.cend ( ) ; }String getName () const { return name ; } };clase Graph { private : HashSet < SharedPtr <Vertex> > vertices ; using VerticesIterator = decltype ( vertices.cbegin ( )); public : ~ Graph ( ) { while ( ! vertices.empty ( ) ) { VerticesIterator vertex = vertices.begin ( ) ; removeVertex ( * vertex ) ; } }void addVertex ( const String & name ) { UniquePtr <Vertex> vertex = std :: make_unique <Vertex> ( name ) ; VerticesIterator iter = vertices.insert ( vertex.get ( ) ) ; }void removeVertex ( SharedPtr < Vertex > vertex ) { vertices . erase ( vertex ); }void addEdge ( SharedPtr <Vertex> from , SharedPtr <Vertex> to ) { // Graph puede acceder a los campos privados de Vertex porque Vertex declaró a Graph como // un amigo. from - > edges.insert ( to ) ; }VerticesIterator begin () const { return vertices . cbegin (); }VerticesIterator end () const { return vertices . cend (); } };

Utilizando amigos variádicos:

importar std ;plantilla < typename ... Friends > clase ClassWithFriends { privado : int secretValue ;amigo Amigos ...; público : explicit ClassWithFriends ( int secret ) : secretValue { secret } {} };clase A { public : void readSecret ( const class ClassWithFriends < A , B >& instance ) const { std :: println ( "Inspeccionando el valor secreto de A: {}" , instance.secretValue ) ; } };clase B { public : void readSecret ( const class ClassWithFriends < A , B >& instance ) const { std :: println ( "Inspeccionando el valor secreto de B: {}" , instance.secretValue ) ; } };int main ( int argc , char * argv [ ] ) { ClassWithFriends < A , B > secretHolder ( 135 ); A a ; B b ; a.readSecret ( secretHolder ) ; b.readSecret ( secretHolder ) ; }

Encapsulación

El uso adecuado de clases amigas aumenta la encapsulación, ya que permite extender el acceso privado de una estructura de datos a sus partes —que le pertenecen— sin permitir el acceso privado a ninguna otra clase externa. De esta forma, la estructura de datos permanece protegida contra intentos accidentales de romper sus invariantes desde fuera.

Es importante destacar que una clase no puede acceder a las partes privadas de otra clase, ya que esto rompería el encapsulamiento. En cambio, una clase otorga acceso a sus partes privadas a otra clase declarándola como amiga. En el ejemplo del grafo, Graph no puede declararse amiga de Vertex. En su lugar, Vertex declara a Graph como amiga, otorgándole así acceso a sus campos privados.

El hecho de que una clase elija a sus propios amigos implica que la amistad no es simétrica en general. En el ejemplo del grafo, Vértice no puede acceder a los campos privados de Grafo, aunque Grafo sí puede acceder a los campos privados de Vértice.

Alternativas

Una característica similar, aunque no equivalente, la proporciona la internalpalabra clave `access modifier` de C#, que permite que las clases dentro del mismo ensamblado accedan a las partes privadas de otras clases. Esto equivale a marcar cada clase como amiga de otra en el mismo ensamblado; las clases amigas ofrecen una granularidad más precisa.

Los lenguajes de programación que carecen de soporte para clases amigas, o una característica de lenguaje similar, tendrán que implementar soluciones alternativas para lograr una interfaz segura basada en partes para una estructura de datos. Ejemplos de dichas soluciones alternativas son:

  • Hacer públicos los campos de las partes. Esta solución reduce el encapsulamiento al permitir que se violen las invariantes de la estructura de datos desde el exterior.
  • Trasladar todos los datos estructurales mutables de la parte a la estructura de datos e introducir una indirección desde cada parte hacia su estructura de datos. Esta solución modifica la organización de la estructura de datos y aumenta el consumo de memoria en casos donde, de otro modo, no sería necesaria esta información.

Propiedades

  • Las amistades no son simétricas  : si una clase Aes amiga de otra clase B, una clase Bno es automáticamente amiga de otra clase A.
  • Las amistades no son transitivas  : si una clase Aes amiga de otra clase B, y otra clase Bes amiga de otra clase C, una clase Ano es automáticamente amiga de otra clase C.
  • Las relaciones de amistad no se heredan  : si una clase Basees amiga de otra clase X, la subclase Derivedno es automáticamente amiga de la primera X; y si una clase Xes amiga de la primera clase Base, la primera clase Xno es automáticamente amiga de la subclase Derived. Sin embargo, si una clase Yes amiga de la subclase Derived, la primera clase Ytambién tendrá acceso a las partes protegidas de la primera clase Base, al igual que la subclase Derived.

Referencias

  1. "9 Más sobre C++" .
  2. Jody Hagins, Arthur O'Dwyer (22 de marzo de 2024). "P2893R3 - Variadic friends" . open-std.org . ISO/IEC 14882 Lenguajes de programación — C++, ISO/IEC JTC1/SC22/WG21.
  • http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8a.doc%2Flanguage%2Fref%2Fcplr043.htm
  • http://www.cplusplus.com/doc/tutorial/inheritance/