Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

domenica 17 febbraio 2013

C orientato agli oggetti! C orientato agli oggetti?
come usare la OOP in C

L'argomento del post di oggi è un ossimoro. Il C è un linguaggio decisamente procedurale (oserei dire che è IL linguaggio procedurale), per cui pensare di fare Object Oriented Programming con il C non ha molto senso... però è possibile, e senza scomodare il figlioletto C++! Presenterò un esempio semplice semplice in C++ (ma proprio semplice), e cercheremo di vedere il suo equivalente in C. Per concludere, vi anticipo, scriverò alcune note filosofiche su C, C++ e OOP. Poi, magari, mentre scrivo il post mi stufo e decido di non scriverle, oppure me ne dimenticherò e non le scriverò (soluzione diplomatica: se scrivo ciò che ho in mente adesso qualcuno potrebbe sentirsi "toccato"...).

Condizione necessaria (e sufficiente), per continuare a leggere il post, è il possesso di alcune nozioni di C++ e OOP: se non le avete iscrivetevi a un corso accelerato di C++ o leggetevi rapidamente un manuale... comunque, data la semplicità dell'esempio, non tentate diventare super-esperti del C++ per finire di leggere il post. Anche perché, in tal caso (super-esperti!), vi potrebbe servire così tanto tempo che potrebbe non esistere più né questo blog né internet... ("life is too long to be good at C++", Erik Naggum).

Veniamo al dunque: scriviamo una classe elementare in C++ che implementa un contatore. Scriveremo, secondo la prassi classica, su tre file: header, implementazione e uso. Vediamo l'header counter.hpp:
/* counter.hpp - header classe CCounter
 */
class CCounter {
private:
    // attributi privati
    int value;

public:
    // metodi pubblici
    CCounter();          // costruttore
    void incValue(int n);
    int getValue();
};
Come promesso è una classe semplicissima, con un attributo privato, due metodi di lettura e scrittura e un costruttore esplicito. Passiamo al file di implementazione, counter.cpp:
/* counter.cpp - implementazione classe CCounter
 */ 
#include "counter.hpp"

// CCounter() - costruttore della classe CCounter
CCounter::CCounter()
{
    value = 0;
}

// incValue() - metodo di incremento attributo value
void CCounter::incValue(int n)
{
    value += n;
}

// getValue() - metodo di lettura attributo value
int CCounter::getValue()
{
    return value;
}
Evidentemente non c'è neanche da spiegare come funzionano i metodi appena descritti (e, se state ancora leggendo, dovreste avere già fatto, con successo, il corso accelerato di C++...). Per ultimo vediamo il file di uso della classe, countermain.cpp:
/* countermain.cpp - esempio d'uso della classe CCounter
 */
#include <iostream>
#include "counter.hpp"

using namespace std;

main()
{
    CCounter cnt_a;
    CCounter cnt_b;

    // leggo valori
    cout << "cnt_a value = " << cnt_a.getValue() << endl;
    cout << "cnt_b value = " << cnt_b.getValue() << endl;

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

    // leggo valori
    cout << "cnt_a value = " << cnt_a.getValue() << endl;
    cout << "cnt_b value = " << cnt_b.getValue() << endl;
}
Ok, l'uso della classe è un po' stupido, ma per quest'esempio è più che sufficiente. Instanziamo due oggetti della classe CCounter, cnt_a e cnt_b, stampiamo i valori di value per entrambi gli oggetti, incrementiamo e ristampiamo: se compilate ed eseguite il programma l'uscita sarà:
cnt_a value = 0
cnt_b value = 0
cnt_a value = 5
cnt_b value = 7
Come vedete, per la magia della OOP e del C++ i due oggetti hanno vita propria, e, alla fine, il campo value ha valore diverso nei due oggetti (merito del mitico puntatore this... ma questo dovreste già saperlo e questo non è un corso di C++, quindi non lo spiegherò).

Tutto questo si può fare anche in C? Si! Beh, non proprio identico, ma quasi. Useremo la stessa struttura a tre file. Cominciamo con l'header ccounter.h:
/* ccounter.h - header classe CCounter in C
 */
typedef struct _ccounter {
    // attributi
    int value;

    // metodi
    void (*construct)(struct _ccounter *this);       // costruttore
    void (*incValue)(struct _ccounter *this, int n);
    int (*getValue)(struct _ccounter *this);
} CCounter;
Somiglia molto a ccounter.hpp, no? Ovviamente non ci sono le parole magiche public e private, e i metodi sono degli splendidi (?) puntatori a funzione, comunque la somiglianza è evidente. passiamo alla implementazione in ccounter.c:
/* ccounter.c - implementazione classe CCounter in C
 */
#include "ccounter.h"

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

// getValue() - metodo di lettura attributo value
static int getValue(CCounter *this)
{
    return this->value;
}

// construct() - costruttore della classe CCounter
static void construct(CCounter *this)
{
    this->incValue = &incValue;
    this->getValue = &getValue;
    this->value = 0;
}

// pubConstruct() - costruttore pubblico della classe CCounter
void pubConstruct(CCounter *this)
{
    this->construct = &construct;
    this->construct(this);
}
Qui le differenze sono più evidenti, però la somiglianza strutturale è altrettanto chiara. Rispetto alla versione C++ dobbiamo fare in maniera esplicita ciò che il compilatore C++ fa implicitamente: a ogni metodo passiamo un puntatore all'oggetto chiamante (che strano, l'ho chiamato this, anche se non so perché, sarà qualcosa di inconscio), i metodi sono assegnati ai puntatori a funzione, e dobbiamo aggiungere un metodo in più, un costruttore pubblico, da chiamare dopo l'instanziazione dell'oggetto (anche questo il compilatore C++ lo fa implicitamente).

Ci resta solo da vedere il file d'uso, ccountermain.c:
/* ccountermain.c - esempio d'uso della classe CCounter in C
 */
#include <stdio.h>
#include "ccounter.h"

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

    CCounter cnt_b;
    pubConstruct(&cnt_b);

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

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

    // leggo valori
    printf("cnt_a value = %d\n", cnt_a.getValue(&cnt_a));
    printf("cnt_b value = %d\n", cnt_b.getValue(&cnt_b));
}
Beh, questo è praticamente identico alla versione C++, salvo la chiamata esplicita al costruttore pubblico, e all'uso della printf() per la stampa dei risultati. Se compilate e eseguite, il risultato (giuro!) è identico a quello della versione C++.

Nel prossimo post, vi mostrerò come sia possibile implementare, in C, anche la ereditarietà delle classi (wow!). Adesso non ne ho voglia, e mi dilungherei troppo (non voglio annoiare nessuno). Anzi, come previsto all'inizio del post, non scriverò neanche le disquisizioni filosofiche... ma anche quelle sono solo rimandate al prossimo post.

Prima di lasciarci non posso evitare di scrivere una nota di riflessione (ma non perdeteci il sonno ripensandoci!): l'esempio appena mostrato serve a poco (anzi, a niente!). Però è una interessante dimostrazione di flessibilità e potenza del nostro amato C, e aggiungo, una dimostrazione reale delle origini del C++: forse non tutti sanno che, moooolti anni fa (anni '80) alcuni dei compilatori C++ disponibili sul mercato erano, in realtà, dei mega-preprocessori, che leggevano il codice C++, lo trasformavano in C, e lo compilavano con un normale compilatore C. Ecco, l'esempio qui sopra è (più o meno...) quello che facevano i compilatori C++ primordiali, quando il C++ era ancora solo un C a oggetti. Meditate gente, meditate...

Ciao e al prossimo post.