Curso de C++ (Página 33)

pagina032 Principal pagina034

CAPITULO 33 Sistema de protección

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

Esto es un concepto de POO, el encapsulamiento hace que cada objeto se comporte de un modo autónomo y que lo que pase en su interior sea invisible para el resto de objetos. Cada objeto sólo responde a ciertos mensajes y proporciona determinadas salidas.

Pero, en ciertas ocasiones, querremos poder acceder a determinados miembros privados de un objeto de una clase desde otros objetos de clases diferentes. C++ proporciona un mecanismo para sortear el sistema de protección. En otros capítulos veremos la utilidad de esta técnica, 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:

Funciones externas amigas

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.h>
#include <stdlib.h>
 
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(int argc, char *argv[])
{
   A Na(10);
 
   Ver(Na);  // Ver el valor de Na.a
   Na.Ver(); // Equivalente a la anterior
   system("PAUSE");  
   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.h>
#include <stdlib.h>
 
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(int argc, char *argv[])
{
   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;
   system("PAUSE");  
   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.h>
#include <stdlib.h>
 
/* 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()                                   /* Constructor inline */
   {
      Cabeza = NULL;                         /* Lista vacía */
   }
   ~Lista() {LiberarLista();}                /* Destructor inline */
   void Nuevo(int tipo);                     /* Insertar figura */
   Elemento *Primero() {return Cabeza;}      /* Obtener primer elemento */
   Elemento *Siguiente(Elemento *p)          /* Obtener el siguiente elemento a p */
      {if(p) return p->sig; else return p;}; /* Si p no es NULL */
   bool EstaVacio() { return Cabeza == NULL;}/* Averiguar si la lista está vacía */

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

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

/* 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(int argc, char *argv[])
{
   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;
   system("PAUSE");  
   return 0;
}

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.

Para poder introducir este ejemplo he usado algunos conceptos que aún no hemos explicado, como los punteros a objetos, o el uso de los operadores new y delete con clases, pero creo que vale la pena para demostrar que la amistad entre clases tiene una aplicación práctica.


pagina032 Principal pagina034