4.11.8c Clases abstractas
§1 Sinopsis
La abstracción es un recurso de la mente (quizás el más característico de nuestra pretendida superioridad respecto del mundo animal). Por su parte, los lenguajes de programación permiten expresar la solución de un problema de forma comprensible simultáneamente por la máquina y el humano. Constituyen un puente entre la abstracción de la mente y una serie de instrucciones ejecutables por un dispositivo electrónico. En consecuencia, la capacidad de abstracción es una característica deseable de los lenguajes artificiales, pues cuanto mayor sea, mayor será su aproximación al lado humano. Es decir, con la imagen existente en la mente del programador. En este sentido, la introducción de las clases en los lenguajes orientados a objetos ha representado un importante avance respecto de la programación tradicional y dentro de ellas, las denominadas clases abstractas son las que representan el mayor grado de abstracción.
De hecho, las clases abstractas presentan un nivel de "abstracción" tan elevado que no sirven para instanciar objetos de ellas. Representan los escalones más elevados de algunas jerarquías de clases y solo sirven para derivar otras clases, en las que se van implementando detalles y concreciones, hasta que finalmente presentan un nivel de definición suficiente que permita instanciar objetos concretos. Se suelen utilizar en aquellos casos en que se quiere que una serie de clases mantengan una cierta característica o interfaz común. Por esta razón a veces se dice de ellas que son pura interfaz.
Resulta evidente en el ejemplo de la figura que los diversos tipos de motores tienen características diferentes. Realmente
tienen poco en común un motor eléctrico de corriente alterna y una turbina de vapor. Sin embargo, la construcción de una
jerarquía en la que todos motores desciendan de un ancestro común, la clase abstracta "Motores", presenta la ventaja
de unificar la interfaz. Aunque evidentemente su definición será tan "abstracta", que no pueda ser utilizada para
instanciar directamente ningún tipo de motor. El creador del lenguaje dice de ellas que soportan la noción de un concepto general
del que solo pueden utilizarse variantes más concretas [2].
§2 Clases abstractas
Una clase abstracta es la que tiene al menos una función virtual pura (como hemos visto, una función virtual es especificada como "pura" haciéndola igual a cero 4.11.8a).
Nota: recordemos que las clases que tienen al menos una función virtual (o virtual pura) se denominan clases polimórficas ( 4.11.8). Resulta por tanto, que todas las clases abstractas son también polimórficas, pero no necesariamente a la inversa.
§3 Reglas de uso:
- Una clase abstracta solo puede ser usada como clase base para otras clases, pero no puede ser instanciada para crear un objeto 1.
- Una clase abstracta no puede ser utilizada como argumento o como retorno de una función 2.
- Si puede declararse punteros-a-clase abstracta 3 [1].
- Se permiten referencias-a-clase abstracta, suponiendo que el objeto temporal no es necesario en la inicialización 4.
§4 Ejemplo
class Figura { // clase abstracta (CA)
point centro;
...
public:
getcentro() { return center; }
mover(point p) { centro = p; dibujar(); }
virtual void rotar(int) = 0; // función (método)
virtual pura
virtual void dibujar() = 0; // función (método)
virtual pura
void brillo(int);
// método normal -definición off-line-
...
};
...
Figura x;
// ERROR: intento de instanciar una CA.1
Figura* sptr;
// Ok: puntero a CA. 3
Figura f();
// ERROR: función NO puede devolver tipo CA. 2
int g(Figura s);
// ERROR: CA NO puede ser argumento de función 2
Figura& h(Figura&); // Ok: devuelve tipo "referencia-a-CA"
4
int h(Figura&);
// Ok: "referencia-a-CA" si puede ser argumento 4
§5 Suponiendo que A sea una clase abstracta y que D sea una clase derivada inmediata de ella,
cada función virtual pura fvp de A, para la que D no aporte una definición, se convierte en función virtual
pura para D. En cuyo caso, D resulta ser también una clase abstracta.
Por ejemplo, suponiendo la clase Figura definida previamente:
class Circulo : public Figura { // Circulo deriva de una C.A.
int radio;
// privado por defecto
public:
void rotar(int); // convierte rotar en función no virtual
};
En esta clase, el método heredado Circulo::dibujar() es una función virtual pura. Sin embargo, Circulo::rotar() no lo es (suponemos que definición se efectúa off-line). En consecuencia, Circulo es también una clase abstracta. En cambio, si hacemos:
class Circulo : public Figura { // Circulo deriva de una C.A.
int radio;
public:
void rotar(int); // convierte rotar en función no virtual pura
void dibujar(); // convierte dibujar en función no virtual pura
};
la clase Circulo deja de ser abstracta.
§6 Las funciones-miembro pueden ser llamadas desde el constructor de una clase abstracta, pero la
llamada directa o indirecta de una función virtual pura desde tal constructor puede provocar un error en tiempo de ejecución.
Sin embargo, son permitidas disposiciones como la siguiente:
class CA { // clase abstracta
public:
virtual void foo() = 0; // foo virtual pura
CA() { // constructor
CA::foo(); // Ok.
};
...
void CA::foo() { // definición en algún sitio
...
}
La razón es la ya señalada ( 4.11.8a), de que la utilización del operador :: de acceso a ámbito anula el mecanismo de funciones virtuales.
[1] Precisamente, la invocación de métodos de clases derivadas mediante punteros a la superclase, es una de las características esenciales de la tecnología COM de Microsoft.