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 claseB, una claseBno es automáticamente amiga de otra claseA. - Las amistades no son transitivas : si una clase
Aes amiga de otra claseB, y otra claseBes amiga de otra claseC, una claseAno es automáticamente amiga de otra claseC. - Las relaciones de amistad no se heredan : si una clase
Basees amiga de otra claseX, la subclaseDerivedno es automáticamente amiga de la primeraX; y si una claseXes amiga de la primera claseBase, la primera claseXno es automáticamente amiga de la subclaseDerived. Sin embargo, si una claseYes amiga de la subclaseDerived, la primera claseYtambién tendrá acceso a las partes protegidas de la primera claseBase, al igual que la subclaseDerived.
Referencias
Enlaces externos
- 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/
- Clase (programación informática)