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...

martes, 19 de febrero de 2013

¡C orientado a objetos! ¿C orientado a objetos?
cómo usar la OOP en C

El tema del post de hoy es un oxímoron. C es un lenguaje absolutamente procedural (me atrevería a decir que es EL lenguaje procedural), por lo que pensar en hacer Object Oriented Programming con C no tiene mucho sentido ... Pero es posible, y sin molestar a su hijo C++! Voy a presentar un ejemplo sencillo sencillo en C++ (absolutamente fácil), y trataremos de ver su equivalente en C. Por último, os anticipo, voy a escribir algunas notas filosóficas sobre C, C++ y programación orientada a objetos. Pero, tal vez, mientras escribo este post me aburro y decido no escribirlas, o, quizás, se me olvida y no las escriba (solución diplomática: si escribo lo que tengo en mente ahora alguien podría sentirse "tocado"...).

Condición necesaria (y suficiente) para continuar con la lectura del post, es la posesión de ciertos conocimientos de C++ y programación orientada a objetos: si no los tenéis podéis inscribiros a un curso intensivo de C++ o leer rápidamente un manual... sin embargo, dada la simplicidad del ejemplo, no tratéis de convertiros en super-expertos de C++ para terminar de leer el post. Porque sino, en este caso (super-expertos!), podríais necesitar tanto tiempo que podría no existir más este blog o internet ... ("life is too long to be good at C++", Erik Naggum).

Vamos al grano: escribimos una clase elementar en C++ que implementa un contador. Escribiremos, de acuerdo con la práctica clásica, en tres ficheros: header, implementación y uso. Vemos el header counter.hpp:
/* counter.hpp - header clase CCounter
 */
class CCounter {
private:
    // atributos privados
    int value;

public:
    // métodos públicos
    CCounter();          // constructor
    void incValue(int n);
    int getValue();
};
Según lo prometido, es una clase simple 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 = 0;
}

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

// getValue() - método de lectura atributo value
int CCounter::getValue()
{
    return value;
}
Obviamente, no hay ni siquiera que explicar cómo funcionan los métodos anteriores (y, si todavía estáis leyendo, ya debería haber terminado con éxito, el curso intensivo de C++...). Finalmente vemos el fichero de uso de la clase, countermain.cpp:
/* countermain.cpp - ejemplo de uso de la clase CCounter
 */
#include <iostream>
#include "counter.hpp"

using namespace std;

main()
{
    CCounter cnt_a;
    CCounter cnt_b;

    // leo valores
    cout << "cnt_a value = " << cnt_a.getValue() << endl;
    cout << "cnt_b value = " << cnt_b.getValue() << endl;

    // incremento valores
    cnt_a.incValue(5);
    cnt_b.incValue(7);

    // leo valores
    cout << "cnt_a value = " << cnt_a.getValue() << endl;
    cout << "cnt_b value = " << cnt_b.getValue() << endl;
}
Ok, el uso de la clase es un poco estúpido, pero para este ejemplo es más que suficiente. Instanciamos dos objetos de la clase CCounter, cnt_a y cnt_b, imprimimos los valores de value para ambos objetos, incrementamos y reimprimimos: si compiláis y ejecutáis el programa la salida será:
cnt_a value = 0
cnt_b value = 0
cnt_a value = 5
cnt_b value = 7
Como se puede ver, por la magia de programación orientada a objetos y del C++ los dos objetos tienen vida propia y, al final, el campo value tiene un valor diferente en los dos objetos (mérito del legendario puntero this... pero esto ya debéis saberlo y esto no es un curso de C++, así que no voy a explicarlo).

¿Todo esto también se puede hacer en C? ¡Si! Bueno, no del todo idéntico, pero casi. Vamos a utilizar la misma estructura en tres ficheros. Vamos a empezar con el header 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;
Se ve muy similar a ccounter.hpp, ¿verdad? Obviamente no hay las palabras mágicas public y private, y los métodos son espléndidos (?) Punteros de función, sin embargo, la similitud es evidente. Pasamos a la implementación en ccounter.c:
/* ccounter.c - implementación clase CCounter en C
 */
#include "ccounter.h"

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

// getValue() - método 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 = 0;
}

// pubConstruct() - constructor publico de la clase CCounter
void pubConstruct(CCounter *this)
{
    this->construct = &construct;
    this->construct(this);
}
Aquí las diferencias son más evidentes, pero la similitud estructural es igualmente clara. En comparación con la versión de C++ tenemos que hacer explícitamente lo que el compilador C++ hace de forma implícita: a cada método se pasa un puntero al objeto llamante (extraño, lo he llamado this, aunque no sé por qué, será algo inconsciente), los métodos se asignan a los punteros de función, y tenemos que añadir un método más, un constructor público que se llamará después de la instanciación del objeto (también esto el compilador de C++ lo hace implícitamente).

Sólo tenemos que ver el archivo de uso, ccountermain.c:
/* ccountermain.c - ejemplo de uso de la clase CCounter en C
 */
#include <stdio.h>
#include "ccounter.h"

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

    CCounter cnt_b;
    pubConstruct(&cnt_b);

    // leo valores
    printf("cnt_a value = %d\n", cnt_a.getValue(&cnt_a));
    printf("cnt_b value = %d\n", cnt_b.getValue(&cnt_b));

    // incremento valores
    cnt_a.incValue(&cnt_a, 5);
    cnt_b.incValue(&cnt_b, 7);

    // leo valores
    printf("cnt_a value = %d\n", cnt_a.getValue(&cnt_a));
    printf("cnt_b value = %d\n", cnt_b.getValue(&cnt_b));
}
Bueno, esto es prácticamente idéntico a la versión C++, a excepción de la llamada explícita al constructor público y el uso de printf() para imprimir los resultados. Si compiláis y ejecutáis, el resultado (¡juro!) es idéntico al  de la versión C++.

En el próximo post, os mostraré cómo se puede implementar, en C, la herencia de clases (¡wow!). Ahora no tengo ganas, y me alargaría demasiado (no quiero aburrir a nadie). Además, como se predijo en el principio del post, no voy a escribir las disquisiciones filosóficas... pero esas, también, están sólo aplazadas hasta el próximo post.

Antes de saludarnos no puedo evitar escribir una pequeña nota de reflexión (pero ¡no perdéis el sueño pensando en esa!): el ejemplo mostrado no tiene mucha utilidad (en realidad, ¡ninguna!). Pero es una interesante demostración de la flexibilidad y potencia de nuestro amado C, y, añado, una demostración real de las orígenes del C++: tal vez, no todo el mundo sabe que hace muuuchos años (años '80) algunos de los compiladores C++ en el mercado eran, en realidad, unos mega-preprocesadores que leían el código C++, lo transformaban en C y lo compilaban, luego, con un normal compilador C. Entonces, el ejemplo anterior representa (más o menos...) lo que hacían los compiladores C++ primordiales, cuando el C++ no era más que un C a objetos. Meditar gente, meditar...

Hasta el próximo post.