Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


 Prev.

4.11.2b  Herencia simple (II)  La cuestión del acceso a miembros de clases

§7  Presentación

Ya hemos indicado que todos los objetos C++ tienen (entre otros) un atributo de visibilidad ( 4.1.4 ) y podemos añadir aquí que el interior de las clases conforma un espacio en el que, con algunas restricciones, todos sus miembros son visibles. Sin embargo, por motivos de seguridad, se ha dotado a los miembros de clases de un atributo adicional denominado accesibilidad. Se trata de una propiedad de tiempo de compilación que determina si un determinado miembro será accesible desde el exterior de la clase. Este atributo está estrechamente relacionado con cuestiones "genéticas" [3], estableciendo si un determinado miembro heredado, podrá ser accesible o no en las clases derivadas.

Nota: como se indicó en el prólogo de las clases ( 4.11), la razón de la existencia de este atributo es dotarlas de un cierto sistema de seguridad, que permita al implementador garantizar que no se realizarán operaciones indebidas o no autorizadas con determinados miembros (privados) de la clase. Además permite mantener una cierta separación e independencia (encapsulamiento) entre la implementación de una clase y la interfaz que ve el cliente-programador [2], lo que a la postre significa que puede cambiarse la implementación sin que se vea afectado el código del usuario.


Además de que la accesibilidad de un miembro no es la misma desde el "exterior" que desde el resto de miembros de la clase (el interior), el hecho de que esté relacionada con la herencia, hace que sea una propiedad algo más compleja que si se aplicara a funciones o variables normales. Para un miembro determinado, depende si es privativo o heredado de la clase antecesora ( 4.11.2b) y, en este último caso, como era su accesibilidad en la clase-base y como se ha transmitido ( 4.11.2b). En este sentido es frecuente referirse a accesibilidad horizontal y vertical. La primera se refiere al acceso a los miembros desde el exterior. La segunda, al acceso desde una clase a los miembros de sus antecesoras en la jerarquía [5].

El resultado de todo esto, es que existen varias modalidades de accesibilidad para los miembros de clases. A su vez, el conjunto de reglas que define como es la acesibilidad de un miembro determinado respecto al resto de miembros y respecto al exterior de la clase, es un asunto lleno de matices (por no decir complejo).

Nota: no confundir la accesibilidad o propiedad de acceso con la visibilidad. Una entidad puede ser visible pero inaccesible, sin embargo, la inversa no es cierta. Una entidad no puede ser accesible si no es visible [7].

Como agravante adicional a la multitud de matices que envuelven estos conceptos, en nuestra modesta opinión, el asunto suele estar pésimamente explicado y peor expresado, resultando que al principiante le cuesta bastante terminar de entender como funciona el asunto. Por ejemplo, frases como: "En principio una clase no puede acceder a los datos privados de otra, pero podría ser muy conveniente que una clase derivada accediera a todos los datos de su clase base" [8] pueden inducir a error, ya que no se refiere literalmente a "los datos de su clase base", sino a sus propios miembros (de la clase derivada) no privativos, que existen en la clase derivada por herencia y no por declaración explícita.

§8  Tipos de miembros

De forma simplificada [4], para fijar ideas, puede decirse que atendiendo a esta accesibilidad, los miembros pueden ser de tres tipos: privados, públicos y protegidos. Como se verá inmediatamente, el tipo de accesibilidad se manifiesta en la propia sintaxis de creación de la clase, y viene determinada por dos atributos que denominamos especificadores de acceso y modificadores de acceso .

§8.1  Miembros privados

Solo son accesibles por miembros de la propia clase; no desde el exterior. Suele decirse de ellos que solo son accesibles por el programador de la clase. "Cuando se deriva una clase, los miembros privados no son accesibles en la clase derivada".

Nota: aunque la práctica totalidad de la bibliografía existente utiliza esta frase, o parecida, para referirse a la transmisión por herencia de este tipo de miembros, en nuestra opinión es desafortunada y oscurece la cabal comprensión del asunto. Los no accesibles no son los miembros privados de la clase antecesora, sino los miembros de la propia clase derivada no privativos (heredados).

Es interesante señalar que los miembros privados de una clase no son accesibles ni aún desde las posibles clases anidadas. Ejemplo:

class X {              // clase contenedora

  typedef int ENTERO;

  int i;

  public:

  typedef float FRACCIONARIO;

  class XX {          // clase anidada

    int i;

    ENTERO ii;        // Error: X::ENTERO es privado

    FRACCIONARIO f;   // Ok:  X::FRACCIONARIO es público

  };

};

§8.2  Miembros públicos

Son accesibles desde el exterior de la clase; pueden ser referenciados desde cualquier sitio donde la clase sea visible y constituyen en realidad su interfaz, por lo que suele decirse de ellos que son accesibles por los usuarios de la clase.

§8.3  Miembros protegidos

Tienen el mismo comportamiento que los privados (no son accesibles desde el exterior de la clase), pero difieren en cuanto a la accesibilidad de sus descendientes.  Los miembros de clases derivadas que son descendientes de miembros protegidos, son accesibles por el resto de los miembros (cosa que no ocurre con los descendientes de miembros privados). Más sobre las formas de transmitirse la visibilidad de los ancestros en las clases derivadas en: Declaración por herencia ( 4.11.2b) y Agregaciones ( 4.11.2c).


§8.4  Las posibilidades de acceso a los diversos tipos están esquematizadas en la figura
 

§9  Especificador opcional de acceso

La declaración de cada miembro de una clase puede incluir un especificador opcional que, en principio, define la accesibilidad del miembro.  C++ tiene tres palabras clave específicas para este fin: publicprivate y protected.

Nota: no confundir este especificador opcional de acceso, que se incluye en la declaración individualizada de los miembros de la clase, con el modificador opcional de acceso que se utiliza en la declaración de clases derivadas ( 4.11.2b) y afecta a la totalidad de los miembros heredados.

Observe que los elementos declarados después de las etiquetas public, private y protected, son de dicho tipo hasta tanto no se defina una nueva etiqueta (los dos puntos : son imprescindibles al final de esta).  Por defecto los elementos de clases son privados y los de estructuras son públicos.

Ejemplo:

class Vuelo {

  char nombre[30];    // private (por defecto)

  int capacidad;      // private (por defecto)

private:

  float peso;         // private

protected:

  void carga(&operacion};    // protected

public:

  void despegue(&operacion}; // public

  void crucero(&operacion);  // public

};

Las propiedades nombre, capacidad y peso solo pueden ser accedidos por las funciones carga, despegue y crucero; estas dos últimas, que a su vez son declaradas públicas, pueden ser accedidas desde el exterior (por los usuarios de la clase).  Resulta evidente que los usuarios externos pueden utilizar nombre, capacidad y peso solo de forma indirecta.

Como puede verse, si prescindimos de cuestiones "Genéticas" (accesibilidad de miembros de los descendientes), en realidad solo existen dos tipos de miembros: públicos y privados. Lo usual es que todas las propiedades (variables) de la clase se declaren privadas, y las funciones (la mayoría) se declaren públicas. En caso que se necesite acceder a las propiedades desde el exterior, el acceso se realizará a través de una función pública. Por ejemplo, si en el caso anterior se necesita acceder a la propiedad nombre del Vuelo, puede definirse una función ex profeso:

class Vuelo {

  char nombre[30];    // private (por defecto)

  int capacidad;      // private (por defecto)

private:

  float peso;         // private

protected:

  void carga(&operacion};    // protected

public:

  void despegue(&operacion}; // public

  void crucero(&operacion);  // public

  char* getName();           // obtener el nombre del vuelo

};


A estas funciones que controlan el acceso a las propiedades de la clase se las denomina accesores ("accessor functions"). Su nombre suele empezar con get (si se trata de obtener el valor) o set (si se trata de modificarlo) y terminan con el nombre de la propiedad accedida.  En el primer caso devuelven un valor del mismo tipo que la variable que se accede.  En el ejemplo anterior podrían ser los métodos:

int getCapacidad () { return capacidad; }

void setCapacidad (int valor) {
  if (valor >= 0 && valor < VMAX )  capacidad = valor;

  else cout << "El valor proporcionado no es correcto!!" << endl;
}

Es bastante frecuente que los accesores sean definidos como funciones in-line.  Algunos programadores utilizan directamente propiedades públicas para aquellas variables que deben ser visibles desde el exterior, lo que es contrario a la buena práctica y solo tiene la justificación de ahorrarse el trabajo de escribir la función adecuada. Además, en ocasiones puede resultar peligroso, ya que el usuario puede utilizar cualquier valor, quizás uno inadecuado. Por ejemplo un cero, que puede aparecer más tarde como cociente de una división, o un valor excesivamente largo en una cadena, que puede ocasionar un problema de desbordamiento de buffer [1]. Por su parte las funciones se suelen declarar públicas, a excepción de las utilizadas para realizar tareas repetitivas dentro de la clase (en realidad funciones auxiliares), que se declaran privadas.

Nota: no hace falta decir que el hecho de no disponer el acceso a la propiedades directamente, sino a través de funciones, no es una cuestión de masoquismo (ganas de trabajar más). Se supone que en la función se implementan los mecanismos necesarios para garantizar que el valor suministrado por el usuario está dentro de los parámetros de seguridad pertinentes.


§9.1  Salvo indicación en contrario, la accesibilidad de un miembro es la misma que la del miembro que le precede.  Es decir, la del último miembro cuya accesibilidad se declaró explícitamente. Por ejemplo:

class Vuelo {
  ...
  protected: void carga(&operacion};    // protected

  void descarga(&operacion};            // protected como el anterior

  void flete{&operacion};               // ídem
  ...
}

Ya se ha indicado que si se alcanza el principio de la declaración de una clase y no aparece ninguna etiqueta explícita, los miembros son privados (públicos si se trata de una estructura).


§9.2  Para facilitar la legibilidad, es costumbre agrupar los miembros del mismo tipo precedidos por los declaradores correspondientes. El orden puede ser cualquiera, creciente o decreciente [6], de forma que la declaración adopta el aspecto siguiente:

class Unaclase {

  private:        // en realidad este especificador no sería necesario

   ...            // declaracion de los miembros privados

  protected:

   ...            // aquí todos los miembros protegidos

  public:

   ...            // todos los miembros públicos

}

§10  Modificador de acceso

Con lo señalado hasta aquí, las propiedades de acceso a los miembros de las clases quedarían suficientemente definidas si estas existieran aisladamente. Pero se ha dicho que en los casos de herencia, menos las excepciones señaladas ( pág.1), todos los miembros de la base directa son heredados por la clase derivada. Surge entonces una cuestión: ¿Como se hereda el atributo de accesibilidad que tiene cada miembro de la base?. La respuesta es "depende".

Para controlar este aspecto de la transmisión de la accesibilidad se utiliza el <mod-acceso> indicado en la sintaxis de la página anterior ( 4.11.2b). Este especificador puede ser cualquiera de las palabras clave ya conocidas: public, protected y private. Su influencia en los miembros de la clase resultante depende del atributo de acceso que tuviese inicialmente el miembro en la superclase (suponemos B la clase-base y D la clase-derivada):

  Modificador public.

Ejemplo:

class D : public B { ... };

  • miembros públicos en B resultan públicos en D.

  • miembros protegidos en B resultan protegidos en D.

  • miembros privados en B resultan privados en D.

  Modificador protected.

Ejemplo:

class D : protected B { ... };

  • miembros públicos en B resultan protegidos en D.

  • miembros protegidos en B resultan protegidos en D.

  • miembros privados en B resultan privados en D.

  Modificador private.

Ejemplo:

class D : private B { ... };

  • miembros públicos en B resultan privados en D.

  • miembros protegidos en B resultan privados en D.

  • miembros privados en B resultan privados en D.

Lo anterior puede esquematizarse en el siguiente cuadro:

Visibilidad del miembro en la clase-base

Modificador de acceso utilizado en la declaración de la clase derivada

public

protected

private

public

public

protected

private (accesible)

protected

protected

protected

private (accesible)

private

private (no accesible directamente)

private (no accesible directamente)

private (no accesible directamente)


Nota
: puesto que las propiedades de acceso de la clase derivada se transmiten a sus descendientes, y la clase-base B puede ser a su vez una clase derivada, el asunto de las propiedades de acceso es el resultado de una cadena en la que es posible remontarse hacia atrás, hasta alcanzar la raíz de las clases antecesoras (las que no derivan de ninguna otra).

  Es interesante resaltar que la acción del modificador de acceso permite reducir, o en el mejor de los casos igualar, la visibilidad de los miembros originales de la superclase, pero nunca aumentarla. Al tratar de la herencia múltiple ( 4.11.2c) veremos que las restricciones generales impuestas por este modificador pueden revertirse individualmente.

§10.1  Modificador por defecto

Salvo indicación explícita en contrario, el modificador de acceso se supone private si se trata de la declaración de una clase y public si de una estructura 4.5). Por ejemplo, las definiciones de C y E que siguen son equivalentes.

class C : private B { <lista-miembros> };

class C : B { <lista-miembros> };

struct E : public B { <lista-miembros> };

struct E : B1 { <lista-miembros> };

Ejemplo

#include <iostream.h>

class B { public: int pub; };       // clase raíz
class D1 : B { public: int pub1; }; // derivada (private por defecto)
class D2 : public B { public: int pub2; };  // derivada

int main (void) {           // ========================
  B bc;                      // instancia de B
  bc.pub = 3;               // Ok: pub es pública
  D1 d1;                    // instancia de D1
  d1.pub1 = 11;             // Ok: d1.pub1 es pública
  d1.pub  = 12;             // Error: d1.pub es privada !!
  D2 d2;                    // instancia de D2
  d2.pub2 = 21;             // Ok: d2.pub2 es pública
  d2.pub  = 22;             // Ok: d2.pub es pública
}


§10.2  Recuerde que los miembros heredados que son privados en la superclase B, son inaccesibles a las funciones miembro de la clase derivada D (y en general a todo el mundo). Por ejemplo:

class B { private: int pri; };     // Clase raíz
class D1 : public B {              // clase derivada
  public:
  int getpri() { return pri; }     // Error: miembro inaccesible
};


La única forma de saltarse esta restricción y acceder a estos miembros, es que en la superclase se haya garantizado explícitamente el acceso mediante la declaración friend ( 4.11.2a1) como se muestra en el siguiente ejemplo:

#include <iostream.h>

class D1;                 // declaración adelantada
class B {                 // clase raíz
  private: int pri;
  friend class D1;
};

class D1 : private B {    // clase derivada
  public:
  int getpri() { return pri; }     // Ok: acceso permitido
  void putpri(int i) { pri = i; }  // ídem.
};

class D2 : private B {    // clase derivada
  public:
  int getpri() { return pri; }     // Error: miembro pri inaccesible
  void putpri(int i) { pri = i; }  // Error: miembro pri inaccesible
};

int main (void) {         // ========================
  D1 d1; // instancia de D
  d1.putpri(7);
  cout << "d1.pri == " << d1.getpri() << endl;
}

Salida (después de eliminar sentencias erróneas):

d1.pri == 7


§10.3  En otras ocasiones caben más estrategias:

#include <iostream.h>

class B {              // clase raíz
  public: int x;
  B(int n = 0) { x = n+1;}  // constructor por defecto
};

class D1 : private B { // clase derivada
  public:
  int y;
  D1(int n = 0) { y = n+2;}
};

class D2 : private B { // clase derivada
  public:
  B::x;
  int y;
  D2(int n = 0) { y = n+3;}
};

class D3 : private B { // clase derivada
  public:
  using B::x;
  int y;
  D3(int n = 0) { y = n+3;}
};

int main (void) { // ========================
  D1 d1(10);
  D2 d2(20);
  D3 d3(30);
//cout << "d1.x == " << d1.x << endl;   Error B::x is not accesible
  cout << "d1.y == " << d1.y << endl;
  cout << "d2.x == " << d2.x << endl;
  cout << "d2.y == " << d2.y << endl;
  cout << "d3.x == " << d3.x << endl;
  cout << "d3.y == " << d3.y << endl;
}

Salida:

d1.y == 12
d2.x == 1
d2.y == 23
d3.x == 1
d3.y == 33

Comentario

El programa declara una superclase B de la que derivan privadamente otras tres.  En cada subclase existen dos propiedades: una x es heredada, la otra y, es privativa. Como consecuencia de la forma de herencia (private) establecida, los miembros públicos en B son privados en las subclases, y por lo tanto, las propiedades x no son accesibles desde el exterior de las subclases. Precisamente esta es la causa del error señalado en la función main.

Para evitar este inconveniente se utilizan dos procedimientos distintos en las subclases D2 y D3:

En D2 se utiliza el sistema tradicional: declarar público el miembro heredado de la superclase (observe la notación utilizada para referirnos a este miembro de la subclase).

En D3 se utiliza el método preconizado por la última revisión del Estándar: utilizar la declaración using para hacer visible un objeto ( 4.1.11c).

§11  Reajuste de propiedades de acceso

Como se ha señalado, las propiedades de acceso de los miembros de las clases base pueden heredarse con ciertas modificaciones mediante los modificadores de acceso de la lista de miembros, pero además, pueden todavía realizarse ajustes particulares utilizando referencias adecuadas en la clase derivada.

Ejemplo:

class B {               // declaración de B por 'definición'
    int a;              // a es privado (por defecto)
  public:
    int b, c;
    int Bfun(void);
};

class X : private B {   // bcBfun son 'ahora' privados en X
    int d;              // d es privado (por defecto)
                         // Observe que a no es accesible en X
  public:
    B::c;               // c, que era privado, vuelve a ser público
    int e;
    int Xfun(void);
};
int Efun(X& x);         // Efun es externa a B y X

Como resultado, puede comprobarse que:

  • La función Efun() puede usar solo nombres públicos: c, eXfun().

  • La función Xfun()definida en X (que a su vez deriva de B como private), tiene acceso a:

    • El entero c definido en B y posteriormente reajustado a public.

    • Miembros de X privados: b y Bfun()

    • Los propios miembros de X públicos y privados: d, e, y Xfun()

  • Desde luego Xfun() no puede acceder a los miembros privados de B, como a.


§11.1  Como se ha indicado anteriormente , el modificador de acceso puede mantener o reducir la visibilidad de los miembros heredados respecto de la que tuviesen en la superclase, pero nunca aumentarla. Además la redefinición solo puede aplicarse para volver a convertir a public o protected, tal como se comprueba en el ejemplo.

class B {               // Superclase
  private: int pri;
  protected: int pro;
  public: int pub;
};

class D : private B {   // pro y pub son 'ahora' privados en D
  public: B::pri;       // Error: intento de aumentar la visibilidad

  protected: B::pri;    // Error: ídem.

  public: B::pro;       // Error: ídem.

  protected: B::pub     // Error: intento de disminuir la visibilidad

  protected: B::pro;    // Ok:  pro vuelve a ser privado en D.

  public: B::pub;       // Ok:  pub vuelve a ser público en D.

};

§12  Excepciones a las reglas de acceso
§12.1  Acceso a miembros estáticos

El especificador private para un miembro de clase, o el especificador de herencia private o protected no tienen efecto para los miembros estáticos ( 4.11.7) de las superclases, como se pone en evidencia en el siguiente ejemplo:

#include <iostream>
using namespace std;

class B {
  static int sb;     // privado por defecto
  int nb;            // ídem.
  public:
  int b;
  B() : nb(11), b(12) {}
};
int B::sb =10;       // inicialización de miembro estático

class C : private B {
  static int sc;
  int nc;
  public:
  int c;
};
int C::sc = 20;

class D: public C {
  public:
  D() {              // constructor
    cout << "miembro B::sb: " << B::sb << endl;   // Ok.
    cout << "miembro B::nb: " << B::nb << endl;   // Error!!

//           cannot access private member declared in class 'B'
    cout << "miembro B::sb: " << B::b  << endl;   // Error!!

//           not accessible because 'C' uses 'private' to inherit from 'B'
    cout << "miembro C::sc: " << C::sc << endl;   // Ok.
    cout << "miembro C::nc: " << C::nc << endl;   // Error!!

//           cannot access private member declared in class 'C'.
  }
};

int main() {       // ================
  D d;
  return 0;
}

Salida después de eliminadas las sentencias erróneas:

miembro B::sb: 10
miembro C::sc: 20

§12.2  Acceso a funciones virtuales

Las funciones virtuales ( 4.11.8a) se utilizan en jerarquías de clases y se caracterizan por tener definiciones distintas en cada clase de la jerarquía que solapan u ocultan las definiciones en las superclases. Esta diferencia de comportamiento (especialmente en subclases hermanas) es lo que les confiere su carácter polimórfico y constituye la justificación de su existencia.

Como se muestra en el siguiente ejemplo, en ocasiones la redefinición puede incluir una modificación de los atributos de acceso respecto de los que tiene en la superclase.  En estos casos la accesibilidad de la función viene determinada por su declaración, y no se ve afectada por la existencia de modificadores que afectaran a la accesibilidad de las versiones de las subclases.

class B {

  public:

  virtual void foo();

};

 

class D: public B {

  private:

  void foo();

};

 

int main() {         // ============

  D d;

  B* bptr = &d;

  D* dptr = &d;

  bptr->foo()        // M4: Ok.

  dptr->foo()        // M5: Error!

  ...

}

La diferencia de comportamiento de ambas invocaciones se justifica en que el compilador realiza el control de acceso en el punto de invocación, utilizando la accesibilidad de la expresión utilizada para designar la función. En M4 se utiliza un puntero a la superclase, y en este punto foo es pública, con lo que el acceso es permitido. En cambio, en la sentencia M5 se utiliza un puntero a la subclase y en esta el método es privado, con lo que el acceso es denegado.

§13  Resumen

La definición de las clases puede incluir unos especificadores de acceso public, private y protected, que determinan la accesibilidad de los miembros . En casos de herencia, la accesibilidad de los miembros heredados puede ser modificada globalmente mediante modificadores de acceso que se aplican a cada una de las posibles bases . Finalmente la accesibilidad resultante de los miembros heredados puede ser retocada individualmente dentro de ciertas limitaciones .

Como puede verse, todo el sistema está diseñado de forma que consigue un gran control sobre las propiedades de acceso de cada uno de los miembros de una clase, sea o no derivada de otra/s, aunque el sistema sea en sí mismo bastante prolijo en detalles. Sobre todo si tenemos en cuenta que se completan con un proceso de semiocultación de miembros que tiene lugar cuando alguno de los miembros de una superclase es redefinido en la clase derivada. Este proceso ha sido estudiado con detalle en la página anterior ( 4.11.2b).

Nota: recordemos que, además de los especificadores de acceso ya señalados, existe una forma adicional de garantizar el acceso a los miembros de una clase mediante palabra clave:  friend ( 4.11.2a1). Son las denominadas clases o funciones amigas o invitadas.


En el siguiente programa se presenta un amplio muestrario de la casuística que puede presentarse. Debe examinar detenidamente el código cotejándolo con los comentarios del final.

#include <iostream.h>
class B {                         // clase raíz
  int pri;                        // privado por defecto
  protected: int pro;
  public: int pub;
  void putpri(int i) { pri = i; }
  void putpro(int i) { pro = i; }
  int getpri() { return pri; }
  int getpro() { return pro; }
};
class D1 : public B {            // deriva de B
  int pri1;
  protected: int pro1;
  public: int pub1;
//int getprib() { pri; }    Error: miembro no accesible
  int getprob() { return pro; }
};
class D2 : private B {          // deriva de B
  int pri2;
  protected: int pro2;
  public: int pub2;
};

int main (void) {         // ========================
  B b0;                   // instancia de B
  b0.putpri(1);           // Ok: acceso a pri mediante método público
  b0.putpro(2);           // Ok: ídem
  b0.pub = 3;             // Ok: pub es público
  cout << "Salida-1 Valores en b0:" << endl;
  cout << " b0.pri == " << b0.getpri() << endl;
  cout << " b0.pro == " << b0.getpro() << endl;
  cout << " b0.pub == " << b0.pub << endl;

  D1 d1;                  // instancia de D1
  d1.putpri(11);          // Ok: acceso a pri mediante método público
  d1.putpro(12);          // Ok: ídem
  d1.pub = 13;            // Ok: pub es público
  d1.pub1 = 14;           // Ok: ídem
  cout << "Salida-2 Valores en d1:" << endl;
  cout << " d1.pri == " << d1.getpri() << endl;
  cout << " d1.pro == " << d1.getpro() << endl;
  cout << " d1.pro == " << d1.getprob() << endl;
  cout << " d1.pub == " << d1.pub << endl;
  cout << " d1.pub1 == " << d1.pub1 << endl;

  D2 d2;                  // instancia de D2
// d2.putpri(21);            Error: función no accesible
// d2.putpro(22);            Error: función no accesible
// d2.pub = 23;              Error: pub no accesible
  d2.pub2 = 24;           // Ok: pub2 es público
  cout << "Salida-3 Valores en d2:" << endl;
// cout << " d2.pri == " << d2.getpri() << endl; Error función no accesible
// cout << " d2.pro == " << d2.getpro() << endl; Error función no accesible
// cout << " d2.pub == " << d2.pub << endl; Error propiedad no accesible
  cout << " d2.pub2 == " << d2.pub2 << endl;
}

Salida:

Salida-1 Valores en b0:
  b0.pri == 1
  b0.pro == 2
  b0.pub == 3
Salida-2 Valores en d1:
  d1.pri == 11
  d1.pro == 12
  d1.pro == 12
  d1.pub == 13
  d1.pub1 == 14
Salida-3 Valores en d2:
  d2.pub2 == 24

Comentario

a:  Cada clase tiene miembros públicos, privados y protegidos; a su vez, la clase base B da lugar a dos descendientes:  D1 y D2 (muy parecidos excepto en el especificador de acceso). Estas dos clases derivadas tienen los mismos miembros que la clase base además de otros privativos.

b:  En la superclase B se definen cuatro funciones (públicas): putpri, putpro, getpri y getpro, que sirven para acceder desde el exterior a los miembros protegidos y privados de la clase. Es típico disponer funciones de este tipo cuando se desea permitir acceso desde el exterior a miembros privados o protegidos de las. Normalmente estas funciones están diseñadas de forma que el acceso solo se realice en los términos que estime convenientes el diseñador de la clase.

No es necesario declararlas de nuevo en las clases derivadas D1, D2 y D3 que ya disponen de ellas por herencia (ver Salida-2 en main).  Sin embargo, observe (que por ejemplo), pri1 y pro1 no son accesibles desde el exterior (para estas propiedades no se han definido funciones análogas a las anteriores).

c:  En la clase D1 se han previsto dos métodos públicos: getprib y getprob, para acceder a las propiedades privadas y protegida de la clase-base. La primera de ellas conduce a un error, ya que los miembros que son privados en la superclase (B) son inaccesibles en la clase derivada, así que D1.pri es inaccesible. La segunda devuelve el miembro protegido D1.pro, que si es accesible, aunque esta función es redundante, devuelve el mismo valor que la función D1.getpro que existe en D1 como herencia pública de B, lo que se demuestra en la Salida-2.

d:  Cuando se pretende repetir en D2 las mismas sentencias que en D1, se producen errores, porque los valores que son accesibles en aquella (por derivar públicamente de la superclase B), aquí son inaccesibles (D2 deriva privadamente de B).

  Inicio.


[1]  Este es un procedimiento muy corriente de "ataque" a aplicaciones, especialmente las que corren en Red. Hoy día prácticamente todas!.

[2]  Precisamente este encapsulamiento es uno de los paradigmas de la POO.

[3]  Para ilustrar más claramente algunas características de los mecanismos de la herencia de clases, en ocasiones utilizaremos una cierta analogía biológica.

[4]  El asunto es algo más complicado que lo aquí expuesto porque, como se ha señalado ( 3.2.1c), el especificador const con miembros de clase también impone un matiz de accesibilidad entre miembros, y el especificador friend afecta igualmente la accesibilidad de los miembros desde el exterior.

[5]  Como señalamos a continuación, en realidad esta expresión es incorrecta (aunque muy gráfica), ya que no se refiere literalmente a "miembos de sus antecesoras", sino a sus propios miembros (de la clase derivada) no privativos (que existen en la clase derivada por herencia y no por declaración explícita).

[6]  Stroustrup aconseja colocar en primer lugar los miembros públicos ( Stroustrup & Ellis:  ACRM §11.1) argumentando que haciéndolo así, si el usuario cambia los miembros privados pero deja intacta su interfaz, el código ya compilado que utiliza solamente los miembros públicos no necesita ser recompilado de nuevo.

[7]  La primera acción del compilador en una expresión para determinar las características de una entidad (identificador), es el proceso de búsqueda de nombres ("Name Lookup" 1.2.1), que está relacionado con la visibilidad. A continuación de efectúa una comprobación estática de tipos. Finalmente se realizan las verificaciones de acceso.

[8]  "Aprenda C++ como si estuviera en primero"  Javier García de Jalón, José Ignacio Rodriguez y otros. Escuela Superior de Ingenieros Industriales. Navarra (España).

 Prev.