En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

viernes, 22 de marzo de 2013

C orientado a objetos II - La herencia
cómo usar la OOP en C - pt.2

Como prometido, volvemos en la escena del delito: después del sorprendente ejemplo de clase en C visto por aquí, hoy intentaremos implementar la herencia de clase en C. Os anticipo que todo lo que sigue es de poca utilidad, pero puede ser un tutorial útil sobre el potencial oculto de nuestro lenguaje favorito. Si no habéis leído la primera parte del post correr a leerla, sobre todo la parte de introducción, de lo contrario podríais pensar que me he vuelto loco para escribir cosas como ésta.

Vamos al grano: escribimos una (otra) clase elemental en C++ que implementa un (otro) contador.. Escribiremos, de acuerdo con la práctica clásica, ficheros de header, implementación y uso. Vemos l'header counter.hpp:
#ifndef COUNTER_HPP
#define COUNTER_HPP
/* counter.hpp - header clase CCounter
 */

class CCounter {
protected:
    // atributos privados
    int value;

public:
    // métodos públicos
    CCounter();          // constructor
    void incValue(int n);
    int getValue();
};
#endif /* COUNTER_HPP */
Una vez más, es una clase muy sencilla con un atributo privado, dos métodos de lectura y escritura y un constructor explícito. Pasamos al archivo de implementación, counter.cpp:
/* counter.cpp - implementación clase CCounter
 */
#include "counter.hpp"

// CCounter() - constructor de la clase CCounter
CCounter::CCounter()
{
    value = 100;
}

// incValue() - método di incremento atributo value
void CCounter::incValue(int n)
{
    value += n;
}

// getValue() - método de lectura atributo value
int CCounter::getValue()
{
    return value;
}
Esta vez, ya que estamos hablando de la herencia de clases, también tendremos una clase derivada, cuyo header es counter_der.hpp:
#ifndef COUNTER_DER_HPP
#define COUNTER_DER_HPP
/* counter_der.hpp - header clase CCounter_der
 */
#include "counter.hpp"

class CCounter_der : public CCounter {
public:
    // métodos públicos
    void incValue(int n);
};
#endif /* COUNTER_DER_HPP */
mientras que el file de implementación es counter_der.cpp:
/* counter_der.cpp - implementación clase CCounter_der
 */
#include "counter_der.hpp"

// incValue() - método de incremento atributo value
void CCounter_der::incValue(int n)
{
    value -= n;
}
Finalmente vemos el file de uso de la clase, countermain.cpp:
/* countermain.cpp - ejemplo de uso de las clases CCounter e CCounter_der
 */
#include <iostream>
#include "counter.hpp"
#include "counter_der.hpp"

using namespace std;

main()
{
    CCounter cnt;
    CCounter_der cnt_der;

    // leo valores
    cout << "cnt value = " << cnt.getValue() << endl;
    cout << "cnt_der value = " << cnt_der.getValue() << endl;

    // incremento valores
    cnt.incValue(5);
    cnt_der.incValue(7);

    // leo valores
    cout << "cnt value = " << cnt.getValue() << endl;
    cout << "cnt_der value = " << cnt_der.getValue() << endl;
}
El uso de la clase es un poco estúpido (como la última vez), pero para este ejemplo es más que suficiente. Creamos una instancia de dos objetos de la clase CCounter, cnt y cnt_der, imprimimos los valores de value para ambos objetos, incrementamos y reimprimimos: si compiláis y ejecutáis el programa la salida será:
cnt value = 100
cnt_der value = 100
cnt value = 105
cnt_der value = 93
Como se puede ver, por la magia de la herencia, el objeto cnt_der (una instancia de la simple CCounter_der) es un contador que disminuye, ya que la clase derivada se diferencia de la clase base (en este caso) solo por el método incValue(), así que al final, el valor del campo se incrementa en cnt y disminuye en cnt_der. Fantástico.

Tener en cuenta, que en los dos header-file he usado el oportuno include guard, para evitar inclusiones múltiples, ya que counter_der.h llama counter.h (de hecho, este ejemplo es un poco más riguroso respeto al del post anterior).

Una última nota: en la clase base el método incValue() no se ha definido virtual, sea porque para el ejemplo de uso propuesto no era necesario (el compilador, en realidad, no se queja...), sea porque cae fuera del objetivo del post: esto no es un blog sobre C++...

¿También la herencia de clase puede ser implementada en C? ¡Si! Pues bien, el resultado final no es (como se verá) muy elegante, pero funciona. Vamos a utilizar la misma estructura de header, implementación y uso. Vamos a empezar con el header ccounter.h:
#ifndef CCOUNTER_H
#define CCOUNTER_H
/* ccounter.h - header clase CCounter en C
 */

typedef struct _ccounter {
    // atributos
    int value;

    // métodos
    void (*construct)(struct _ccounter *this);    // constructor
    void (*incValue)(struct _ccounter *this, int n);
    int (*getValue)(struct _ccounter *this);
} CCounter;

// prototipos globales
void pubConstruct(CCounter *this);
#endif /* CCOUNTER_H */
Como se ha indicado en estas páginas es muy similar a ccounter.hpp. Pasamos ahora a la implementación en ccounter.c:
/* ccounter.c - implementación clase CCounter en C
 */
#include "ccounter.h"

// incValue() - método di incremento attributo value
static void incValue(CCounter *this, int n)
{
    this->value += n;
}

// getValue() - metodo de lectura atributo value
static int getValue(CCounter *this)
{
    return this->value;
}

// construct() - constructor de la clase CCounter
static void construct(CCounter *this)
{
    this->incValue = &incValue;
    this->getValue = &getValue;
    this->value = 100;
}

// pubConstruct() - constructor público de la clase CCounter
void pubConstruct(CCounter *this)
{
    this->construct = &construct;
    this->construct(this);
}
Como se ha indicado en el otro post, en comparación con la versión C++ tenemos que hacer explícitamente lo que el compilador de C++ hace  implícitamente: a cada método se pasa un puntero al objeto llamante, los métodos se asignan a los punteros de función, y tenemos que añadir método más, un constructor público, que hay que llamar después de la instanciación del objeto.

Y ahora llegamos a la parte más interesante, la clase derivada CCounter. Vemos el header ccounter_der.h:
#ifndef CCOUNTER_DER_H
#define CCOUNTER_DER_H
/* ccounter_der.h - header clase CCounter en C
 */
#include "ccounter.h"

typedef struct _ccounter_der {
    // atributos
    CCounter cnt_base;

    // métodos
    void (*construct)(struct _ccounter_der *this);    // constructor
    void (*incValue)(struct _ccounter_der *this, int n);
} CCounter_der;

// prototipos globales
void pubConstruct_der(CCounter_der *this);
#endif /* CCOUNTER_DER_H */
Como se puede ver es una versión simplificada de la cabecera de la clase base. En los atributos hay un objeto de tipo CCounter (este es el truco que nos permite hablar de herencia) y en los métodos se encuentra el usual constructor más el único método a redefinir (como en la versión C + +).

Pasamos ahora, a la implementación en ccounter.c:
/* ccounter.c - implementación clase CCounter_der en C
 */
#include "ccounter_der.h"

// incValue() - método de incremento atributo value
static void incValue(CCounter_der *this, int n)
{
    this->cnt_base.value -= n;
}

// construct() - constructor de la clase CCounter
static void construct(CCounter_der *this)
{
    this->incValue = &incValue;
}

// pubConstruct() - constructor público de la classe CCounter
void pubConstruct_der(CCounter_der *this)
{
    pubConstruct(&this->cnt_base);
    this->construct = &construct;
    this->construct(this);
}
Creo que el código es muy claro: también esta es una versión más pequeña de la implementación de la clase base, con la redefinición del método incValue() y su asignación. Tener en cuenta que el constructor publico llama al constructor público de la clase base. Sólo tenemos que ver el fichero de uso, ccountermain.c:
/* ccountermain.c - ejemplo de uso de la clase CCounter en C
 */
#include <stdio.h>
#include "ccounter.h"
#include "ccounter_der.h"

main()
{
    CCounter cnt;
    pubConstruct(&cnt);

    CCounter_der cnt_der;
    pubConstruct_der(&cnt_der);

    // leo valores
    printf("cnt value = %d\n", cnt.getValue(&cnt));
    printf("cnt_der value = %d\n", cnt_der.cnt_base.getValue(&cnt_der.cnt_base));

    // incremento valores
    cnt.incValue(&cnt, 5);
    cnt_der.incValue(&cnt_der, 7);

    // leo valores
    printf("cnt value = %d\n", cnt.getValue(&cnt));
    printf("cnt_der value = %d\n", cnt_der.cnt_base.getValue(&cnt_der.cnt_base));
}
Pues bien, también esta vez es virtualmente idéntico a la versión C++, a excepción de la llamada explícita al constructor público, y al uso de printf() para imprimir los resultados. Si compiláis y ejecutáis, el resultado (¡juro otra vez!) es idéntico al  de la versión C++. Como se quería demostrar, también  con el C podemos usar (¿simular? ¿emular?) la herencia de clases.

Ya que me he extendido un poco con el código, y, como siempre, no quiero aburrir a nadie, dejo para el próximo post las disquisiciones filosóficas, que había prometido, sobre C vs C++ (sí, no me he olvidado...), donde voy a tratar de ser lo más diplomático posible...

Hasta el próximo post.