32 Sistema de protección

Ya sabemos que los miembros privados de una clase no son accesibles para funciones y clases exteriores a dicha clase.

Este es uno de los conceptos de POO, el encapsulamiento, que tiene como objetivo hacer que lo que pase en el interior de cada objeto sea inaccesible desde el exterior, y que el comportamiento de otros objetos no pueda influir en él. Cada objeto sólo responde a ciertos mensajes y proporciona determinadas salidas.

Pero, en ciertas ocasiones, necesitaremos tener acceso a determinados miembros de un objeto de una clase desde otros objetos de clases diferentes, pero sin perder ese encapsulamiento para el resto del programa, es decir, manteniendo esos miembros como privados.

C++ proporciona un mecanismo para sortear el sistema de protección. En otros capítulos veremos la utilidad de esta técnica, pero de momento sólo explicaremos en qué consiste.

Declaraciones friend

El modificador friend puede aplicarse a clases o funciones para inhibir el sistema de protección.

Las relaciones de "amistad" entre clases son parecidas a las amistades entre personas:

  • La amistad no puede transferirse, si A es amigo de B, y B es amigo de C, esto no implica que A sea amigo de C. (La famosa frase: "los amigos de mis amigos son mis amigos" es falsa en C++, y probablemente también en la vida real).
  • La amistad no puede heredarse. Si A es amigo de B, y C es una clase derivada de B, A no es amigo de C. (Los hijos de mis amigos, no tienen por qué ser amigos míos. De nuevo, el símil es casi perfecto).
  • La amistad no es simétrica. Si A es amigo de B, B no tiene por qué ser amigo de A. (En la vida real, una situación como esta hará peligrar la amistad de A con B, pero de nuevo me temo que en realidad se trata de una situación muy frecuente, y normalmente A no sabe que B no se considera su amigo).

Funciones amigas externas

El caso más sencillo es el de una relación de amistad con una función externa.

Veamos un ejemplo muy sencillo:

#include <iostream>
using namespace std;
 
class A {
  public:
    A(int i=0) : a(i) {}
    void Ver() { cout << a << endl; }
  private:
    int a;
    friend void Ver(A); // "Ver" es amiga de la clase A
};
 
void Ver(A Xa) {
   // La función Ver puede acceder a miembros privados
   // de la clase A, ya que ha sido declarada "amiga" de A
   cout << Xa.a << endl;
}

int main() {
   A Na(10);
 
   Ver(Na);  // Ver el valor de Na.a
   Na.Ver(); // Equivalente a la anterior

   return 0;
}

Como puedes ver, la función "Ver", que no pertenece a la clase A puede acceder al miembro privado de A y visualizarlo. Incluso podría modificarlo.

No parece que sea muy útil, ¿verdad?. Bueno, seguro que en alguna ocasión tiene aplicaciones prácticas.

Funciones amigas en otras clases

El siguiente caso es más común, se trata de cuando la función amiga forma parte de otra clase. El proceso es más complejo. Veamos otro ejemplo:

#include <iostream>
using namespace std;
 
class A; // Declaración previa (forward)
 
class B {
   public:
    B(int i=0) : b(i) {}
    void Ver() { cout << b << endl; }
    bool EsMayor(A Xa);  // Compara b con a
   private:
    int b;
};

class A {
   public:
    A(int i=0) : a(i) {}
    void Ver() { cout << a << endl; }
   private:
    // Función amiga tiene acceso 
    // a miembros privados de la clase A
    friend bool B::EsMayor(A Xa); 
    int a;
};

bool B::EsMayor(A Xa) {
   return b > Xa.a;
}

int main() {
   A Na(10);
   B Nb(12);
   
   Na.Ver();
   Nb.Ver();
   if(Nb.EsMayor(Na)) cout << "Nb es mayor que Na" << endl;
   else cout << "Nb no es mayor que Na" << endl;
   
   return 0;
}

Puedes comprobar lo que pasa si eliminas la línea donde se declara "EsMayor" como amiga de A.

Es necesario hacer una declaración previa de la clase A (forward) para que pueda referenciarse desde la clase B.

Veremos que estas "amistades" son útiles cuando sobrecarguemos algunos operadores.

Clases amigas

El caso más común de amistad se aplica a clases completas. Lo que sigue es un ejemplo de implementación de una lista dinámica mediante el uso de dos clases "amigas".

#include <iostream>
using namespace std;
 
/* Clase para elemento de lista enlazada */
class Elemento {
  public:
   Elemento(int t);                           /* Constructor */
   int Tipo() { return tipo;}                /* Obtener tipo */
  private:                                         /* Datos: */
   int tipo;                                         /* Tipo */
   Elemento *sig;                      /* Siguiente elemento */
  friend class Lista;                   /* Amistad con lista */
};

/* Clase para lista enlazada de números*/
class Lista {
  public:
   Lista() : Cabeza(NULL) {}                  /* Constructor */
                                              /* Lista vacía */
   ~Lista() { LiberarLista(); }                /* Destructor */
   void Nuevo(int tipo);                  /* Insertar figura */
   Elemento *Primero()            /* Obtener primer elemento */
   { return Cabeza; }            
   /* Obtener el siguiente elemento a p */
   Elemento *Siguiente(Elemento *p) {
      if(p) return p->sig; else return p;}; 
   /* Si p no es NULL */
   /* Averiguar si la lista está vacía */
   bool EstaVacio() { return Cabeza == NULL;}

  private:
   Elemento *Cabeza;           /* Puntero al primer elemento */
   void LiberarLista(); /* Función privada para borrar lista */
};

/* Constructor */
Elemento::Elemento(int t) : tipo(t), sig(NULL) {}
  /* Asignar datos desde lista de parámetros */

/* Añadir nuevo elemento al principio de la lista */
void Lista::Nuevo(int tipo) {
   Elemento *p;
   
   p = new Elemento(tipo);  /* Nuevo elemento */
   p->sig = Cabeza;
   Cabeza = p;
}

/* Borra todos los elementos de la lista */
void Lista::LiberarLista() {
   Elemento *p;
   
   while(Cabeza) {
      p = Cabeza;
      Cabeza = p->sig;
      delete p;
   }
}

int main() {
   Lista miLista;
   Elemento *e;
   
   // Insertamos varios valores en la lista   
   miLista.Nuevo(4);
   miLista.Nuevo(2);
   miLista.Nuevo(1);

   // Y los mostramos en pantalla:
   e = miLista.Primero();
   while(e) {
      cout << e->Tipo() << " ,";
      e = miLista.Siguiente(e);
   }
   cout << endl;
   
   return 0;
}

Ejecutar este código en codepad.

La clase Lista puede acceder a todos los miembros de Elemento, sean o no públicos, pero desde la función "main" sólo podemos acceder a los miembros públicos de nuestro elemento.

Palabras reservadas usadas en este capítulo

friend.

Comentarios de los usuarios (11)

JuDelCo
2011-01-05 20:56:44

Se pueden definir varias amistades para una misma clase? Si es así, se declaran todas en la misma linea o linea por linea cambiando el nombre? Es decir

friend class Lista1,Lista2,Lista3;

o bien...

friend class Lista1;

friend class Lista2;

friend class Lista3;

Es la duda que tengo

Salvador Pozo
2011-01-10 16:35:16

Se pueden definir tantas amistades como sea necesario para cada clase. La forma correcta de definirlas es la segunda:

friend class Lista1;

friend class Lista2;

friend class Lista3;

Es decir, una en cada línea. No está permitido separar los identificadores de cada clase con comas.

aJ
2011-03-08 22:36:16

Tengo unas dudas con el ejemplo de Clases amigas en la parte de :

Elemento *sig;
Elemento *Primero();
Elemento *Siguiente(Elemento *p);
Elemento *cabeza;

Por ejemplo en 'Elemento *sig;' es un puntero que apunta a la clase Elemento ?.. y en '*Siguiente(Elemento *p);' no tengo idea de lo que es, por que involucra un puntero a función ?..

Saludos

Steven
2011-03-09 00:07:57

Hola AJ,

Elemento *sig;

Sí; 'sig' es un puntero a 'Elemento'.

Elemento *Siguiente( Elemento *p );

Aquí tenemos la función 'Siguiente()', que acepta un puntero a 'Elemento' y retorna un puntero a 'Elemento'. Recuerda que la sintaxis del prototipo de una función es la siguiente:

<tipo_retorno> <nombre_función> ( <lista_de_parámetros> );

Aplicando el prototipo de 'Siguiente()' a esta sintaxis vemos que tenemos,

<tipo_retorno> = Elemento *
<nombre_función> = Siguiente
<lista_de_parámetros> = Elemento *

Si quieres, puedes reescribir el prototipo así:

Elemento * Siguiente( Elemento *p );

O incluso,

Elemento* Siguiente( Elemento *p );

Espero haber aclarado las dudas.

Steven

aJ
2011-03-09 00:59:28

Gracias por responderme Steven ya aclare las dudas.

Javier R.
2013-07-29 01:10:45

Hola de nuevo amigos de esta estupenda web. Tengo una duda con respecto a las funciones friend. ¿Qué ocurre si deseo que una misma función sea amiga de varias clases?

¿Sería algo como esto?

class clas1{
  private:
    float val1;
    float val2;
    friend void calcula();
  };
class clas2{
  private:
    float val1;
    float val2;
    friend void calcula();
  };

Muchas gracias por su ayuda.

Steven R. Davidson
2013-07-29 04:35:51

Hola Javier,

Sí; esto funciona. De hecho, puedes hacer la prueba escribiendo un programa que haga esto mismo, para que veas el resultado.

Hasta pronto,

Steven

Javier R.
2013-07-29 06:16:05

Ok. Muchas gracias por tan pronta respuesta. Saludos :-)

Javier R.
2013-07-29 19:18:07

Hola de nuevo. Molestándolos con una nueva duda respecto a la amistad de las clases. Tengo esto:

class clase1{
  public:
    clase1();
  friend class clase2;
};

class clase2{  
  public:
    clase2():valor(0){}
    short dval(){return valor;}
  private:
    short valor;
}

Si en main creo un objeto de la clase 1:

clase1 obj;

¿Es posible por medio de este objeto acceder al método dval() de clase2?

Lo he intentado haciendo esto obj.dval() pero me marca error, diciendo que el método dval() no pertenece a la clase 1. Entonces ¿Cómo puedo desde un objeto de la clase1 acceder a un método (o valor) de la clase 2?

Nuevamente muchas gracias por su valiosa ayuda.

Steven R. Davidson
2013-07-29 21:14:24

Hola Javier,

La amistad no representa la relación que describes, que por cierto se llama herencia; y la veremos en el capítulo 36. La amistad sirve para saltarse el sistema de protección de miembros, cuando accedas a ellos. Esto no significa que los miembros de una clase amiga automáticamente pertenezcan a otra clase. Cada clase describe sus miembros para sí misma.

Lo que sí podrías hacer es acceder directamente a los miembros de un objeto de la clase 'clase1' desde 'clase2'. Por ejemplo,

class clase2;  // Declaración adelantada

class clase1
{
public:
  clase1();

private:
  int dato;

friend class clase2;
};

class clase2
{
public:
  clase2():valor(0){}
  short dval(){return valor;}

  clase1 &func( clase1 &ref )  { ref.dato = valor; }

private:
  short valor;
};

Ahora podemos hacer esto:

clase1 obj1;
clase2 obj2;

obj2.func( obj1 );

Como puedes ver, la amistad es de 'clase1' a 'clase2'; es decir, 'clase1' le da permiso absoluto a 'clase2'. En el ejemplo ampliado, cualquier objeto de 'clase2' puede invocar 'func()' que internamente modifica el miembro privado, 'dato', desde 'clase2' - su amiga.

Espero que esto aclare la duda.

Steven

Javier R.
2013-07-29 21:29:17

Sí, me has aclarado mi duda. Muchas gracias por tu tiempo y ayuda. Saludos. :-)