[UCR]  
[/\]
Universidad de Costa Rica
Escuela de Ciencias de la
Computación e Informática
[<=] [home] [<>] [\/] [=>]

Clases abstractas no polimórficas para C++

Non polymorphic C++ abstract classes

Adolfo Di Mare




Resumen [<>] [\/] [/\]

Abstract [<>] [\/] [/\]

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.
We describe how to get reimplemented the methods of a C++ base class in its derived class. This solution uses templates instead of polymorphism (virtual methods), which sometimes allows the program constructor to get a better implementation.

Versión CISCI-2009 --- BASE-CISCI-2009.pdf --- BASE-CISCI-2009.ppt


Introducción [<>] [\/] [/\]

Introduction [<>] [\/] [/\]

      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. By using abstract classes a C++ designer can build classes that serve as templates of derived classes for client programmers of his or her modules. Abstract classes are a language mechanism that allows the compiler to verify that methods have been reimplemented complying with their specification. In this paper it is shown that the expressive power of C++ allows to achieve the same thing by using templates and thus avoiding the cost of virtual functions, which may be useful in many applications where memory and execution time have high priority.

Las plantillas C++ como sustitución del polimorfismo [<>] [\/] [/\]

C++ templates as a substitution for polymorphism [<>] [\/] [/\]

1
2
3
4
5
class Base {
    public: virtual void doIt() = 0;
};
// ...
Base boom; // Error
|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". If "Base" is an abstract class is because it contains a virtual or polymorphic method "Base::doIt()"which is not implemented. It will not be possible to create objects of type "Base" and if any were created, execution of its method "doIt()" would cause a runtime error because this method has not been defined (because in the VMT, the virtual methods table for class "Base", the pointer to the method "doIt()" invokes a routine error.) In Figura 1 it is shown that the compiler detects this type of error if any client programmer tries to declare an object of type "Base".
      La diferencia entre "definir" y "declarar" un objeto en C++ es que las declaraciones se ponen en los archivos de encabezados <*.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) )
      The difference between "defining" and "declaring" an object in C++ is that declarations belong to header files <*.h> and <*.hpp>, while the definitions belong to implementation files <*.c> and <*.cpp>.
  • Declarations correspond to the specification.
  • Definitiones correspond to the implementation.
It is easier to memorize this fact associating the word "definition" with the #define directive used to implement C++ macros:
      #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
      En la Figura 2 se define la función de biblioteca "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.       In Figura 2 the library function "funBase<>()" is defined as a template even even before classes "TheBase" and "Derived" get declared. It will be after this function is invoked that the compiler will be able to apply the template and instantiate the function to determine if there are errors. Then class "TheBase" is declared containing the method "setVal()", but it is declared protected (and it is not implemented anywhere). As "setVal()" is a protected method, if any client programmer declares an object of type "TheBase", when invoking method "setVal()" or when using library function "funBase<>()" the compiler will emit an error. The compiler accepts without problems this whole block of code, but when declaring variable "val" of type "TheBase" the compiler detects several errors and emits the corresponding messages.
14
15
16
17
18
void call_error() {
    TheBase val;     // Error !!!
    val.setVal( 12 );
    funBase( val );
}
|  |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).       In Figura 3 it is shown that the error message the compiler emits does not say that class "TheBase" has been used to declare a variable, as it is explicitly said when trying to instantiate the abstract class "Base" in Figura 1. It is mentioned that there is an improper use of a protected method, which is visible only to derived classes: as function "call_error()" is not a method in a derived class, appropriately the compiler emits the error. The only difference in the implementation of this function and "call_ok()" is the incorrect usage of class "TheBase" instead of class "Derived" for which method "setVal()" is defined. In other words, using the trick of declaring protected in the base class the method you want to be reimplemented in every derived class, you get support for the compiler to verify that in every derived class the correct specification and implementation has been used. "TheBase" is an abstract class implemented without using polymorphism. (Even if the implementation for this operation is included the compiler would issue an error).

Usos de clases abstractas no polimórficas [<>] [\/] [/\]

Use of non polymorphic abstract clases [<>] [\/] [/\]

      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.       The first application of non polymorphic abstract classes is using them in the classroom, to show students the advantages of separating the specification from the implementation of a module. To show several different implementations of the same object it is enough to declare it as an abstract base class, including only its operations, to later incorporate the apropiate definitions of methods and functions for each implementation.
             +---------------+
             | 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 una biblioteca de funciones, en las que posiblemente se encuentren las funciones "isScalar<>()" o "isDiagonal<>()" que verifican alguna propiedad de la matriz utilizando únicamente los métodos públicos de la implementación.       Class "Matrix_BASE<>" shown in Figura 4 contains the most important operations for matrices. In derived classes, which in this case are "Matrix_Dense<>" and "Matrix_Sparse<>", the methods of the base class get reimplemented. To complement this example library of functions can be used, amongst which functions "isScalar<>()" or "isDiagonal<>()" would possibly be found to verify some matrix property using only public methods in the implementation.
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.       Figura 5 is the implementation for library function "isDiagonal<>()" that uses the public methods of the class to determine whether or not the matrix is a diagonal matrix. When defining the constant "ZERO" it is necessary to use the "typename" C++ keyword to tell the compiler that identifier "value_type" is the name of a type or a class, as template "isDiagonal<>()" must be compiled before compiling any use of the version of the classes to which this function will be applied, and thus the compiler has no way of guessing that "value_type" is a type. In practice it will happen that the matrix "Matrix_BASE<>" will be defined in header file "Matrix_BASE.h" and the library functions will be defined in another header file, for example, "Matrix_Lib.h", which will not necessarily be always used by all programmers. Even the order of inclusion of these 2 files is irrelevant, since it is valid to include one or the other first.
      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.       Another important example of the use of this technique is to amalgamate access to files. For this we must define the base access class "FILE_Base<>" that contains the more important access verbs, such as "open<>()" and "close<>()". Then, the specific implementation for each type of access is included in the derived classes.
#include <iostream> // cout, etc
struct Person {
    char name[26];
    int  age;
};

template <class X>
class FILE_Base {
    X m_buffer;
protected:
    void open( const char* fileName );
    void close();
    void find( const char * key );
    bool hasNext();
    void write( const X& val  );
public:
    void set( const X& val ) { m_buffer = val; }
    X* operator->() { return & m_buffer; }
};
template <class X>
class FILE_bt: public FILE_Base<X> {
    void *Btrieve_FBLOCK;
public:
    void open( const char* fName ) {}
    void close() {}
    void find( const char * key ) {}
    bool hasNext() { return false; }
    void write( const X& val) {}
};

void use_Btrieve() {
    FILE_bt< Person > F;
    F.open( "data_file.bt" );
    {   using namespace std;
        F.find( "Phoebe" );
        while ( F.hasNext() ) {
            cout << F->name << F->age;
        }
    }
    F.close();
}
template <class X>
class FILE_dbf: public FILE_Base<X> {
    void *DBase_BUFF;
public:
    void open( const char* fName ) {}
    void close() {}
    void find( const char * key ) {}
    bool hasNext() { return false; }
    void write( const X& val) {}
};

void use_DBase() {
    FILE_dbf< Person > F;
    F.open( "data_file.dbf" );
    {   using namespace std;
        F.find( "Phoebe" );
        while ( F.hasNext() ) {
            cout << F->name << F->age;
        }
    }
    F.close();
}
Figura 6
      En la Figura 6 se muestra cómo 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 [DiM-1994], y se puede usar para concretar una clase genérica para acceso a tablas o bases de datos.       In Figura 6 it is showwn how we can amalgamate access to Btrieve and dBase files using the common interface defined in "FILE_Base<>". This approach compares very favorably with others as described in [DiM-1994], and can be used to get a generic class to access tables or databases.

Detalles de implementación [<>] [\/] [/\]

Implementation details [<>] [\/] [/\]

      Trasladar a la práctica la idea básica expresada en la Figura 2 requiere de un poco de ingenio pues es inevitable encontrar problemas al usar el lenguaje para algo más allá de lo que fue esperado por su diseñador [Str-1998]. La organización de las funciones amigas y los métodos en la clase permite ver cuáles son los métodos abstractos cuya implementación está definida en la clase base "Matrix_BASE<>". En el código fuente están agrupadas de manera que sea sencillo reutilizarlas, pues en un solo bloque de código están todas las implementaciones juntas. Además, el constructor por omisión de la clase sí tiene que estar definido porque el compilador lo necesita para inicializar la base de todo objeto de tipo "Matrix_BASE<>"; lo mismo sucede con el destructor. Lo demás métodos no están implementados pues son métodos abstractos, cuya implementación debe definirse en las clases derivadas.       Taking into practice the basic idea expressed in Figura 2 requires a little ingenuity as it is inevitable to find problems when using the language for anything beyond what was anticipated by its designer [Str-1998]. The organization of friends functions and methods in the class helps to see which are the abstract methods whose implementation is defined in the base class "Matrix_BASE<>". In the source code they are grouped in a way that makes it easy to reuse, as all implementations are together a single block of code. Furthermore, the default constructor for the class itself must be defined because the compiler needs to initialize the base of any object of type "Matrix_BASE<>"; the same happens with the destructor. The other methods are not implemented because they are abstract methods, whose implementation must be defined in derived classes.
      Otra dificultad surge con la especificación del operador de suma para matrices, cuya declaración natural es ésta:
  template <class MAT>
  MAT operator+( const MAT& A, const MAT& B );
Desafortunadamente, cuando se usa la matriz junto con otras clases, como por ejemplo "std::string<>" u otra similar, esta definición produce un conflicto de ambigüedad, pues esta misma definición también calza como operador para la clase "string<>". La forma de evitar este choque es declarar "A", el primer parámetro, de un tipo marcado por la matriz. Con esta modificación, ya la plantilla de matrices no calza con parámetros de otro tipo y así queda eliminado el conflicto con la clase "string<>". Esta es la declaración del primer parámetro "A":
  const Matrix_BASE<typename MAT::value_type>& A;
Hay que usar punteros para transformar esta referencia a la clase base en una referencia a la clase derivada; por eso la implementación incluye este renglón:
  MAT Res = *( (MAT*)(&A) );
que deja en la variable "Res" una copia del primer parámetro "A".
      Another difficulty arises with the specification for the addition operator for matrices, whose natural declaration is:
  template <class MAT>
  MAT operator+( const MAT& A, const MAT& B );
Unfortunately, when the matrix is used with other classes, such as "std::string<>" or any similar, this definition produces a conflict of ambiguity, because this definition also fits as an operator for class "string<>". The way to avoid this clash is to declare "A", the first parameter, of a type tagged by the matrix. With this modification, the matrix template will not fit with other parameters of another type and thus the conflict with the class "string<>" will be removed. This is the declaration of the first parameter "A":
  const Matrix_BASE<typename MAT::value_type>& A;
Pointers must be used to cast the reference to the base class as a reference to derived class; this is why the implementation includes this line:
  MAT Res = *( (MAT*)(&A) );
which leaves in variable "Res"a copy of the first parameter "A".
/// A+B
template <class MAT> inline MAT operator- (
  const Matrix_BASE< typename MAT::value_type >& A,
  const MAT& B
) {
    MAT Res = *( (MAT*)(&A) );
    add_Matrix(Res,B);
    return Res;
}
/// Res += M
template <class MAT>
void add_Matrix( MAT& Res, const MAT& M ) {
    // verifica que las dos matrices sean del mismo tamaño
    assert( "Matrix_BASE<E>::add()" && (Res.rows()==M.rows()) );
    assert( "Matrix_BASE<E>::add()" && (Res.cols()==M.cols()) );

    for ( unsigned i=0 ; i<Res.rows() ; ++i ) {
        for ( unsigned j=0 ; j<Res.cols() ; ++j ) {
            Res.operator()(i,j) += M(i,j);
        }
    }
    return;
}
Figura 7
      En la parte superior de la Figura 7 está la implementación de la operación de suma en la que se usa el primer parámetro "A" de la rutina como una etiqueta que evita que la plantilla sea instanciada para tipos que no están derivados de la clase "Matrix_BASE<>"; el segundo parámetro "B" tiene el tipo correcto para ambos parámetros. Debido a que el tipo del parámetro "A" es "Matrix_BASE<>", esta versión de "operator+<>()" solo se puede aplicar a objetos derivados de la clase "Matrix_BASE<>" y, por eso, no hay posibilidad de ambigüedad cuando se usan matrices y otros tipos para los que ha sido sobrecargado "operator+<>()".       The implementation of the addition operation is at the top of Figura 7 where the first parameter "A" is used as a tag to that keeps the template from being instantiated for types that are not derived from class "Matrix_BASE<>"; the second parameter "B" has the correct type for both parameters. Because the type of the parameter "A" is "Matrix_BASE<>", this version of "operator+<>()" applies only to objects derived from class "Matrix_BASE<>" and, therefore, there is no possibility ambiguity when using matrices and other types for which "operator+<>()" has been overloaded.
      En esta implementación de la suma de matrices se usa la función auxiliar "add_Matrix<>()" para hacer el trabajo de sumar (la diferencia entre "función" y "método" es que la función nunca tiene un parámetro "this", pues existe independientemente de cualquier clase, mientras que el método siempre está relacionado a una clase). Esta función es una implementación abstracta de la suma de matrices, la que puede ser reutilizada en las clases concretas derivadas de "Matrix_BASE<>", a menos que sea más conveniente utilizar una implementación específica más eficiente. Por ejemplo, como la clase "Matrix_Dense<>" está implementada usando un vector unidimensional que contiene todos los valores de la matriz, la implementación más eficiente consiste en recorrer los 2 vectores por sumar para obtener el resultado. Esta implementación contrasta con la usada para "Matrix_Sparse<>", que sí reutiliza la implementación de la clase abstracta.       In this implementation of the addition for matrices the auxiliary function "add_Matrix<>()" is used to do the job of adding (the difference between a "function" and a "method" is that the function never has a parameter "this", because it exists independently of any class, while a method is always related to a class). This function is an abstract implementation of matrix addition, which can be reused in the concrete classes derived from "Matrix_BASE<>", unless it is more convenient to use a more efficient specific implementation. For example, as class "Matrix_Dense<>" is implemented using a vector containing all matrix values, the most efficient implementation is to traverse the 2 vectors to add and obtain the result. This contrasts with the implementation used for "Matrix_Sparse<>", where the implementation of the abstract class is reused.
template <class T>
void add_Matrix( Matrix_Dense<T>& Res, const Matrix_Dense<T> & M ) {
    // verifica que las dos matrices sean del mismo tamaño
    assert( "Matrix_Dense<E>::add()" && (Res.rows()==M.rows()) );
    assert( "Matrix_Dense<E>::add()" && (Res.cols()==M.cols()) );

    T *pRes =   Res.m_val;
    T *pM   = & M.m_val[0];
    T *pEND = & Res.m_val[M.m_cols * M.m_rows];
    for ( ; pRes != pEND; ++pRes, ++pM ) {
        *pRes += *pM;
    }
    return;
}
Figura 8
      Si cualquier programador utiliza matrices de tipo "Matrix_Sparse<>" para sumarlas, el compilador usará la plantilla de la suma de la parte superior de la Figura 7 y, por ende, también instanciará la plantilla de que está en la parte inferior de esa misma figura. En contraste, si la matriz es de tipo "Matrix_Dense<>", el compilador siempre usará la misma plantilla para "operator+<>()" pero usará la implementación de "add_Matrix<>()" de la Figura 8 porque es más específica que la implementación general que aparece en la parte de abajo de la Figura 7. De esta manera no es necesario reimplementar un método abstracto si la implementación provista en la clase base es adecuada, pero sí es posible aportar una implementación específica en los casos en que esa sea el mejor enfoque.       If any programmer uses matrices of type "Matrix_Sparse<>" to add them, the compiler will use the upper part of Figura 7 as the template for addition and, thus, it will also instantiate the template at the bottom of the same figure. In contrast, if the matrices are of type "Matrix_Dense<>", the compiler will still use the same template for "operator+<>()" but it will use the implementation for "add_Matrix<>()" in Figura 8 because it is more specific than the general implementation that appears at the bottom of Figura 7. In this way there is no need to reimplement an abstract method if the implementation provided in the base class is appropriate, but it is possible to provide a specific implementation in cases where this is the better approach.
      Un problema que surge al usar "Matrix_BASE<>" como etiqueta para marcar el parámetro de la suma es que el compilador no puede hacer la conversión automática de un escalar en una matriz, por lo que este código no compila:
      V = 2 + A;
Hay que usar una conversión explícita invocando directamente al constructor de la clase:
      V = Matrix_List<unsigned>(2) + A;
Esta limitación no es exclusiva del uso de clases abstractas no polimórficas, sino que es compartida por cualquier clase C++ para la que se usen operadores sobrecargados (también es correcto argumentar que para evitar errores cualquier programador debe ser consciente de la conversión de un escalar en una matriz y, por eso, no conviene que esa conversión sea automática).
      A problem arises when using "Matrix_BASE<>" as a tag for the parameter in the addition is that the compiler can not do the automatic conversion of a scalar into a matrix, so this code will not compile:
      V = 2 + A;
An explicit conversion directly invoking the constructor of the class must be used:
      V = Matrix_List<unsigned>(2) + A;
This limitation is not exclusive to the use of non polymorphic abstract classes, but is shared by any C++ class where overloaded operators are used (it is also valid to argue that, to avoid errors, any programmer should be aware of conversions from a scalar to a matrix, which makes inconvenient for this conversion to be automtatic).
      Es perfectamente válido definir campos en la clase abstracta. Sin embargo, para el caso específico de esta matriz, no hace falta incorporar ningún campo común en "Matrix_BASE<>" por lo que esa clase solo tiene miembros que son métodos.       It is perfectly valid to define fields in the abstract class. However, for the specific case of this matrix, there is no need to include any common fields in "Matrix_BASE<>" so the members for this class are just methods.
      Es una buena práctica de construcción de programas especificar y documentar módulos, usando herramientas como Doxygen [VH-2005]. Si se usa el enfoque descrito en [DiM-2008] para definir las especificaciones, ayuda mucho el uso del comando "\copydoc" que permite copiar la especificación de los métodos abstractos de la clase base en la documentación de la clase derivada. En la implementación de "Matrix_BASE<>" y todas sus clases derivadas se ha aprovechado esta facilidad de Doxygen.       It is a good program construction practice to specify and document modules using tools such as Doxygen [VH-2005]. Using the approach described in [DiM-2008] to define the specifications, it helps a lot to use command "\copydoc" that allows you to copy the specification for the abstract methods in the base class into the documentation for the derived class. In implementing "Matrix_BASE<>" and all its derived classes this Doxygen facility has been used.
      Es interesante expandir este trabajo agregándole restricciones al código similares a las expuestas en [Mey-2008] o aprovechar nuevas constucciones sintácticas del lenguaje como los "conceptos" para las plantillas C++ que han sido propuestos para el nuevo estándar para el lenguaje C++ [STD-2008]. El uso de etiquetas para los operadores aritméticos es un paso en esa dirección.       It is interesting to further this work adding restrictions similar to those explained in [Mey-2008] or taking advantage of new syntactic constructions for the language such as "concepts" for C++ templates that have been proposed for the new C++ standard [STD-2008]. The use of tags for arithmetic operators is a step in that direction.

Método de uso [<>] [\/] [/\]

Usage method [<>] [\/] [/\]

      Para obtener una clase abstracta no polimórfica se pueden seguir estas prácticas de programación:
  • Los métodos abstractos deben ser declarados "protected".
  • Los métodos abstractos no deben ser implementados para que el compilador detecte el uso incorrecto de la clase abstracta base.
  • Los constructores y destructores de la clase abstracta base deben ser públicos para evitar errores de compilación.
  • Es posible incluir implementaciones para algunos métodos abstractos usando funciones emplantilladas amigas, en las que se incluya un parámetro que sustituya al argumento "this" de los métodos. Por ejemplo, para el método abstracto "set<>(a,b)" se puede implementar la función "set_BASE<>(S,a,b)" que en donde "S" es "*this".
  • La reutilización del código implementado en la clase abstracta se logra invocando a la función amiga de cada método. Este es un ejemplo de cómo implementar el método en la clase derivada:
    template <class E>
    void Derived<E>::set(int a, int b) {
        set_BASE<E>(S,a,b);
    }
    
  • Ayuda bastante acomodar el código de manera que queden juntas las operaciones abstractas no polimórficas.
  • Es conveniente utilizar el comando "\copydoc" de Doxygen para reutilizar la especificación definida para los métodos abstractos no polimórficos.
  • El uso de clases abstractas no polimórficas requiere de un buen conocimiento de las cualidades y restricciones de la parametrización en C++, por lo que el programador que vaya a usar esta forma de programación debe conocer bien cómo usar plantillas.
Los ejemplos que se mencionan al final de este artículo pueden ayudar a quienes opten por usar esta técnica de programación en programas reales.
      To get a non polymorphic abstract class these programming practices can be followed:
  • Abstract methods must be declared "protected".
  • Abstract methods must not be implemented for the compiler to detect the incorrect use of the abstract base class.
  • Constructors and destructors for the abstract base class should be public to avoid compile errors.
  • It is possible to include implementations for some of the abstract methods using friend template functions, which must include a parameter to replace the "this" argument for methods. For example, for the abstract method "set<>(a,b)" can be implemented with function "set_BASE<>(S,a,b)" where "S" is "*this".
  • The reuse of code implemented in the abstract class is achieved by invoking the friend function for each method. This is an example of how to implement the method in the derived class:
    template <class E>
    void Derived<E>::set(int a, int b) {
        set_BASE<E>(S,a,b);
    }
    
  • It helps a lot to rearrange code so that non polymorphic abstract operations are together.
  • It is convenient to use the Doxygen's "\copydoc" command to reuse the specification defined for non polymorphic abstract methods.
  • The use of non polymorphic abstract classes requires a good knowledge of the strengths and limitations of parameterization in C++, so the developer who will use this type of programming should know how to use templates.
The examples mentioned at the end of this article can help those who choose to use this programming technique in actual programs.

Conclusiones [<>] [\/] [/\]

Conclusions [<>] [\/] [/\]

      Una de las contribuciones más importantes de la programación orientada a los objetos [OOP] es mostrar la importancia de separar la especificación de la implementación. Al utilizar clases abstractas no polimórficas se obtiene el beneficio de las clases abstractas sin obligar al programador a pagar el costo de uso de métodos virtuales, lo que son invocados indirectamente a través de punteros almacenados en cada instancia de un objeto. El enfoque aquí expuesto tiene al menos 2 aplicaciones inmediatas:
  • Permite implementaciones diversas de una clase.
  • Permite amalgamar el acceso a archivos diversos.
      One of the most important contributions of object-oriented programming [OOP] is to show the importance of separating the specification from the implementation. By using non polymorphic abstract classes the benefit of the abstract classes is gotten without forcing the developer to pay the cost of using virtual methods, which are invoked indirectly through pointers stored in each instance of an object. The approach presented here has at least 2 immediate applications:
  • It allows different implementations of a class.
  • It allows to consolidate access to various files.
      La primera aplicación es muy relevante en cursos de programación pues le permite a los profesores mostrar varias implementaciones para la misma clase. La segunda le puede facilitar el trabajo a programadores profesionales que necesitan construir módulos o bibliotecas eficientes y portátiles para muchas plataformas. Es importante combinar esta práctica de programación con el uso de herramientas de documentación como Doxygen, para facilitar la especificación y documentación de módulos de programación.       The first application is very important in programming courses because it enables teachers to display multiple implementations for the same class. The second one may facilitate the work of professional programmers who need to build modules or libraries, efficient and portable to many platforms. It is important to combine this programming practice with tools for documentation such as Doxygen, to facilitate the specification and documentation of programming modules.
     

Agradecimientos [<>] [\/] [/\]

Acknowledgements [<>] [\/] [/\]

      David Chaves y Alejandro Di Mare aportaron varias observaciones y sugerencias importantes para mejorar este trabajo. David Chaves and Alejandro Di Mare made several observations and important suggestions to improve this work.


Código fuente [<>] [\/] [/\]

Source code [<>] [\/] [/\]

BASE.zip: [.zip]
http://www.di-mare.com/adolfo/p/BASE/BASE.zip
http://www.di-mare.com/adolfo/p/BASE/es/index.html
http://www.di-mare.com/adolfo/p/BASE/en/index.html

Matrix_BASE<>: [.es] [.en] [.h] [.txt]
http://www.di-mare.com/adolfo/p/BASE/es/classMx_1_1Matrix__BASE.html
http://www.di-mare.com/adolfo/p/BASE/en/classMx_1_1Matrix__BASE.html
Matrix_Dense<>: [.es] [.en] [.h] [.txt]
http://www.di-mare.com/adolfo/p/BASE/es/classMx_1_1Matrix__Dense.html
http://www.di-mare.com/adolfo/p/BASE/en/classMx_1_1Matrix__Dense.html
Matrix_List<>: [.es] [.en] [.h] [.txt]
http://www.di-mare.com/adolfo/p/BASE/es/classMx_1_1Matrix__List.html
http://www.di-mare.com/adolfo/p/BASE/en/classMx_1_1Matrix__List.html
Matrix_Sparse<>: [.es] [.en] [.h] [.txt]
http://www.di-mare.com/adolfo/p/BASE/es/classMx_1_1Matrix__Sparse.html
http://www.di-mare.com/adolfo/p/BASE/en/classMx_1_1Matrix__Sparse.html

Matrix_Lib<>: [.es] [.en] [.h] [.txt]
http://www.di-mare.com/adolfo/p/BASE/es/Matrix__Lib_8h.html
http://www.di-mare.com/adolfo/p/BASE/en/Matrix__Lib_8h.html
Gaussian_Elimination<>: [.es] [.en] [.h] [.txt]
http://www.di-mare.com/adolfo/p/BASE/es/Gaussian__Elimination_8h.html
http://www.di-mare.com/adolfo/p/BASE/en/Gaussian__Elimination_8h.html

test_Matrix.cpp: [.es] [.en] [.cpp] [.txt]
http://www.di-mare.com/adolfo/p/BASE/es/test__Matrix_8cpp.html
http://www.di-mare.com/adolfo/p/BASE/en/test__Matrix_8cpp.html
test_Gauss.cpp: [.es] [.en] [.cpp] [.txt]
http://www.di-mare.com/adolfo/p/BASE/es/test__Gauss_8cpp.html
http://www.di-mare.com/adolfo/p/BASE/en/test__Gauss_8cpp.html

Doxygen:
ftp://ftp.stack.nl/pub/users/dimitri/doxygen-1.5.8-setup.exe

Bibliografía [<>] [\/] [/\]

Bibliography [<>] [\/] [/\]


           
[DiM-1994] Di Mare, Adolfo: "genridx.h" Una interfaz uniforme para el uso de archivos indizados en C, Revista Acta Académica, Universidad Autónoma de Centro América, Número 15, pp [35-58], ISSN 1017-7507, Noviembre 1994.
      http://www.di-mare.com/adolfo/p/genridx.htm
      http://www.di-mare.com/adolfo/p/src/genridx.zip
      http://www.uaca.ac.cr/actas/1994nov/genridx.htm
[DiM-2008] Di Mare, Adolfo: Uso de Doxygen para especificar módulos y programas, I Congreso Internacional de Computación y Matemática, (CICMA-2008), celebrado del 21 al 23 de agosto en la Universidad Nacional (UNA), Costa Rica, I.S.B.N.: 978-9968-9961-1-5, 2008.
      http://www.di-mare.com/adolfo/p/Doxygen.htm
[Mey-2008] Meyers, Scott: Enforcing Code Feature Requirements in C++, Artima Developers, 2008.
      http://www.artima.com/cppsource/codefeatures.html
[STD-2008] (c) ISO/IEC: Working Draft, Standard for Programming Language C++, N2798=08-0308, 2008.
      http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2798.pdf
[Str-1998] Stroustrup, Bjarne: The C++ Programming Language, 3rd edition, Addison-Wesley; 1998.
      http://www.research.att.com/~bs/3rd.html
[VH-2005] van Heesch, Dimitri: Doxygen, 2005.
      http://www.doxygen.org/index.html

Indice [<>] [\/] [/\]

Index [<>] [\/] [/\]

[-] Resumen
[1] Introducción
[2] Las plantillas C++ como sustitución del polimorfismo
[3] Usos de clases abstractas no polimórficas
[4] Detalles de implementación
[5] Método de uso
[6] Conclusiones
[-] Agradecimientos
[-] Código fuente

Bibliografía
Indice
Acerca del autor
Acerca de este documento
[/\] Principio [<>] Indice [\/] Final
[-] Abstract
[1] Introduction
[2] C++ templates as a substitution for polymorphism
[3] Use of non polymorphic abstract clases
[4] Implementation details
[5] Usage method
[6] Conclusions
[-] Acknowledgements
[-] Source code

Bibliography
Index
About the author
About this document
[/\] Top [<>] Index [\/] Bottom

Acerca del autor [<>] [\/] [/\]

About the author [<>] [\/] [/\]

Adolfo Di Mare: Investigador costarricense en la Escuela de Ciencias de la Computación e Informática [ECCI] de la Universidad de Costa Rica [UCR], en donde ostenta el rango de Profesor Catedrático. Trabaja en las tecnologías de Programación e Internet. Es Maestro Tutor del Stvdivm Generale de la Universidad Autónoma de Centro América [UACA], en donde ostenta el rango de Catedrático. Obtuvo la Licenciatura en la Universidad de Costa Rica, la Maestría en Ciencias en la Universidad de California, Los Angeles [UCLA], y el Doctorado (Ph.D.) en la Universidad Autónoma de Centro América.
Adolfo Di Mare: Costarrican Researcher at the Escuela de Ciencias de la Computación e Informática [ECCI], Universidad de Costa Rica [UCR], where he is full professor. Works on Internet and programming technologies. He is Tutor at the Stvdivm Generale in the Universidad Autónoma de Centro América [UACA], where he is Cathedraticum. Obtained the Licenciatura at UCR, and the Master of Science in Computer Science from the University of California, Los Angeles [UCLA], and the Ph.D. at the Universidad Autónoma de Centro América.
[mailto]Adolfo Di Mare <adolfo@di-mare.com>

Acerca de este documento [<>] [\/] [/\]

About this document [<>] [\/] [/\]

Referencia: Di Mare, Adolfo: Clases abstractas no polimórficas para C++, Artículo No. C088PB de la Octava Conferencia Iberoamericana en Sistemas, Cibernética e Informática, CISCI-2009, realizada en Orlando, Florida, EE.UU., julio 2009.
Internet: http://www.di-mare.com/adolfo/p/BASE.htm
http://www.iiis.org/CDs2008/CD2009CSC/CISCI2009/PapersPdf/C088PB.pdf
http://www.iiis.org/CDs2008/CD2009CSC/CISCI2009/Abstract.asp?myurl=C088PB.pdf
Autor: Adolfo Di Mare <adolfo@di-mare.com>
Contacto: Apdo 4249-1000, San José Costa Rica
Tel: (506) 2511-8000       Fax: (506) 2438-0139
Revisión: ECCI-UCR, Enero 2009
Visitantes:

Copyright © 2009 Adolfo Di Mare
Derechos de autor reservados © 2009 Adolfo Di Mare <adolfo@di-mare.com>
[home] [<>] [/\]