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.