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: public, private 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> };
#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 { // b, c, Bfun 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, e y Xfun().
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).
[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).