PDF de programación - Clases abstractas no polimórficas para C++

Imágen de pdf Clases abstractas no polimórficas para C++

Clases abstractas no polimórficas para C++gráfica de visualizaciones

Publicado el 18 de Enero del 2017
820 visualizaciones desde el 18 de Enero del 2017. Una media de 7 por semana
107,8 KB
6 paginas
Creado hace 10a (13/05/2009)
Clases abstractas no polimórficas para C++

Adolfo Di Mare



Universidad de Costa Rica

Escuela de Ciencias de la Computación e Informática

adolfo.dimare@ecci.ucr.ac.cr


RESUMEN
Se describe cómo lograr que en la clase derivada C++ sean
reimplementados los métodos de su clase base. Esta solución usa
plantillas en lugar de polimorfismo (métodos virtuales) lo que, en
algunas ocasiones, le permite al constructor de programas lograr
una mejor implementación.



Palabras clave
Clases abstractas, abstracción, especificación, implementación,
ocultamiento de datos.

1. INTRODUCCIÓN



Mediante el uso de clases abstractas es posible lograr que el
diseñador C++ construya clases que sirven como plantilla para las
clases derivadas que usarán los programadores clientes de sus
módulos. Las clases abstractas son un mecanismo del lenguaje
que le permite al compilador verificar que la reimplementación de
métodos se ha hecho cumpliendo con su especificación. En este
escrito se muestra que la potencia expresiva de C++ permite
lograr lo mismo usando plantillas y evitando así pagar el costo de
funciones virtuales, lo que puede ser útil en muchas aplicaciones
en las que la memoria o el tiempo de ejecución tienen gran
prioridad.

2. LAS PLANTILLAS C++ COMO

SUSTITUCIÓN DEL POLIMORFISMO



class Base {
public: virtual void doIt() = 0;
};
// ...
Base boom; // Error

1
2
3
4
5
|5| error: cannot declare variable `boom' to be of type `Base'
|5| error: because the following virtual functions are abstract:
|2| error: virtual void Base::doIt()
Figura 1

Si "Base" es una clase abstracta es porque contiene algún
método "Base::doIt()" virtual o polimórfico que no está
implementado. No es posible crear objetos de tipo "Base"; si
alguno fuera creado, al ejecutarle su método "doIt()" se
produciría un error en tiempo de ejecución porque ese método no
ha sido definido (pues en la VMT, la tabla de métodos virtuales
de la clase "Base", el puntero para el método "doIt()" invoca
una rutina de error). En la Figura 1 se muestra que el compilador
detecta este tipo de error si cualquier programador cliente trata de
declarar un objeto de tipo "Base".

La diferencia entre "definir" y "declarar" un objeto en C++ es
que las declaraciones se ponen en los archivos de encabezados



Las declaraciones corresponden a la especificación.
Las definiciones corresponden a la implementación.

<*.h> y <*.hpp>, mientras que las definiciones están en los
archivos de implementación <*.c> y <*.cpp>.


Para facilitar memorizar este hecho vale la pena asociar la
palabra "definición" con la directiva #define que sirve para
implementar macros en C++:
#define max(a,b) ( (a)>(b) ? (a) : (b) ).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

template <class X>
void funBase( X & obj ) {
obj.setVal( 5 );
}

class TheBase {
protected: void setVal( int arg );
};

class Derived : public TheBase {
public: void setVal( int arg ) { /* ... */ }
};

void call_ok() {
Derived val; // ok
val.setVal( 12 );
funBase( val );
}

Figura 2

la Figura 2 se define

la función de biblioteca
En
"funBase<>()" como una plantilla, aún antes de declarar las
clases "TheBase" y "Derived". Será cuando esa función sea
invocada que el compilador podrá aplicar la plantilla e instanciar
la función para determinar si hay errores. Luego está declarada la
clase "TheBase" que contiene al método "setVal()", pero
está declarado como protegido
(y además no ha sido
implementado en ningún sitio). Como "setVal()" es un método
protegido, si cualquier programador cliente declara un objeto de
tipo "TheBase", al invocar el método "setVal()" o al usar la
función de biblioteca "funBase<>()" se produciría un error de
compilación. El compilador acepta sin problemas todo este bloque
de código, pero si se declara la variable "val" con el tipo
"TheBase" el compilador detecta varios errores y emite los
mensajes correspondientes.

void call_error() {
TheBase val; // Error !!!
val.setVal( 12 );
funBase( val );
}

14
15
16
17
18

| |In function `void call_error()':
| 7| error: `void TheBase::setVal(int)' is protected
|16| error: within this context
| |In function `void funBase(X&) [with X = TheBase]':
|17| instantiated from here
| 7| error: `void TheBase::setVal(int)' is protected
| 3| error: within this context

Figura 3

En la Figura 3 se muestra el mensaje de error que el
compilador emite no dice que la clase "TheBase" ha sido usada
para declarar una variable, como sí está explícitamente dicho
cuando se trata de instanciar la clase abstracta "Base" en la
Figura 1. Sí se hace referencia a que es impropio utilizar un
método protegido, que es visible únicamente para clases
derivadas: como la función "call_error()" no es un método
de una clase derivada, apropiadamente el compilador emite el
error. La única diferencia de la implementación de esta función y
"call_ok()" es el uso incorrecto de la clase "TheBase" en
lugar de la clase "Derived" para la que el método "setVal()"
sí está definido. En otras palabras, usando el truco de declarar
como protegido en la clase base el método que se desea
reimplementar para toda clase derivada, pero no definiéndolo, se
obtiene el apoyo del compilador para que éste verifique que en la
clase derivada se ha usado
la correcta especificación e
implementación. "TheBase" es una clase abstracta implementada
sin usar polimorfismo. (Aún si se incluye la implementación para
esta operación se obtendría un error de compilación).

3. USOS DE CLASES ABSTRACTAS NO

POLIMÓRFICAS



La primera aplicación de las clases abstractas no polimórficas
es usarlas en el aula, para mostrarle a los estudiantes las ventajas
de separar la especificación de la implementación de un módulo.
Para mostrar varias implementaciones diferentes del mismo objeto
basta declararlo como una clase abstracta base, incluyéndole
únicamente sus operaciones, para luego incorporar en cada
implementación la definición de los métodos y funciones
apropiados.

+---------------+
| Matrix_BASE<> |
+---------------+

/ \

+----------------+ +-----------------+
| Matrix_Dense<> | | Matrix_Sparse<> |
+----------------+ +-----------------+

template <class E>
class Matrix_BASE {
public:
typedef E value_type;
E & operator() (unsigned, unsigned);
friend
Matrix_BASE operator+ (
const Matrix_BASE& A,
const Matrix_BASE& B );
}

Figura 4

En la Figura 4 se muestra la clase "Matrix_BASE<>" que
contiene las operaciones más importantes para matrices. En las
clases derivadas, que en este caso son "Matrix_Dense<>" y
"Matrix_Sparse<>", están reimplementados los métodos de
la clase base. Como complemento a este ejemplo se puede usar
las que posiblemente se
una biblioteca de funciones, en
"isScalar<>()"
encuentren
o
"isDiagonal<>()" que verifican alguna propiedad de la
matriz utilizando únicamente
la
implementación.

los métodos públicos de

las

funciones



template <class Mat>
bool isDiagonal( const Mat& M ) {
if ( M.rows() != M.cols() ) {
return false;
}
typename Mat::value_type ZERO = 0;
for (unsigned i=1; i < M.rows(); i++) {
for (unsigned j=0; j < i; j++) {
if ( M(i,j) != ZERO ) {
return false;
} else if (M(j,i) != ZERO) {
return false;
}
}
}
return true;
}

Figura 5

En la Figura 5 está la implementación de la función de
biblioteca "isDiagonal<>()" que utiliza los métodos públicos
de la clase para determinar si la matriz es o no una matriz
diagonal. Al definir la constante "ZERO" es necesario usar la
palabra reservada C++ "typename" para que informarle al
compilador que el identificador "value_type" es el nombre de
un tipo o de una clase, pues es la plantilla "isDiagonal<>()"
debe ser compilada antes compilar cualquier uso de la versión de
las clases a la que esta función será aplicada y, por ende, el
compilador no tiene forma de adivinar que "value_type" es un
tipo. En la práctica ocurrirá que la matriz "Matrix_BASE<>"
seguramente esté definida en el archivo de encabezado
"Matrix_BASE.h" y la biblioteca de funciones seguramente
estará definida en otro archivo de encabezado, por ejemplo
"Matrix_Lib.h", el que no necesariamente será usado siempre
por todos los programadores. Inclusive el orden de inclusión de
estos 2 archivos es irrelevante, pues es válido incluir uno u otro de
primero.

Otro ejemplo importante del uso de esta técnica es amalgamar
el acceso a archivos. Para esto hay que definir la clase base de
acceso "FILE_Base<>" que contiene los verbos de acceso más
importantes, como "open<>()" y "close<>()". Luego, en las
clases derivadas se incluye la implementación específica para cada
tipo de acceso.

En la Figura 6 (de la siguiente página) se muestra como es
posible amalgamar el acceso a archivos Btrieve y DBase usando la
interfaz común definida en "FILE_Base<>". Este enfoque se
compara muy favorablemente con otros como el expuesto en [1],
y se puede usar para concretar una clase genérica para acceso a
tablas o bases de datos.
  • Links de descarga
http://lwp-l.com/pdf1989

Comentarios de: Clases abstractas no polimórficas para C++ (0)


No hay comentarios
 

Comentar...

Nombre
Correo (no se visualiza en la web)
Valoración
Comentarios
Es necesario revisar y aceptar las políticas de privacidad

Revisar política de publicidad