Introduzione alle classi e agli oggetti in C++

Introduzione alle classi e agli oggetti in C++

Una delle caratteristiche che ha reso celebre il C++ è il supporto alla programmazione orientata agli oggetti. Questo paradigma propone di organizzare il codice attorno a entità chiamate oggetti, che racchiudono insieme i dati e le operazioni che agiscono su di essi. Definendo tipi di dato personalizzati che modellano i concetti del problema, il programma diventa più espressivo, più modulare e più facile da manutenere. In questo articolo introdurremo i concetti fondamentali di classe e oggetto.

Da struct a class

Il punto di partenza è la possibilità di raggruppare più valori correlati in un unico tipo. La parola chiave struct, ereditata dal C, permette di definire un tipo composto da più campi. Ogni variabile di quel tipo, detta istanza, possiede una propria copia dei campi.

#include <iostream>
#include <string>

// Tipo composto che raggruppa dati correlati
struct Point {
    double x;
    double y;
};

int main() {
    Point origin;
    origin.x = 0.0;
    origin.y = 0.0;

    std::cout << origin.x << ", " << origin.y << std::endl;
    return 0;
}

La parola chiave class è molto simile a struct, ma con una differenza fondamentale nel comportamento predefinito riguardo alla visibilità dei membri, come vedremo a breve. Per convenzione, si usa class quando il tipo possiede sia dati sia comportamenti e si vuole controllare l'accesso ai suoi componenti interni.

Membri dati e metodi

Una classe può contenere non solo dati, chiamati membri dati o attributi, ma anche funzioni, chiamate metodi o funzioni membro. I metodi operano sui dati dell'oggetto a cui appartengono e ne definiscono il comportamento. Questo legame tra dati e operazioni è il principio dell'incapsulamento.

#include <iostream>

class Counter {
public:
    int value = 0;   // Membro dato

    // Metodo che incrementa il contatore
    void increment() {
        value++;
    }
};

int main() {
    Counter c;
    c.increment();
    c.increment();
    std::cout << c.value << std::endl;   // Stampa 2
    return 0;
}

Specificatori di accesso

Gli specificatori di accesso regolano la visibilità dei membri di una classe. I membri dichiarati public sono accessibili da qualunque parte del programma, mentre quelli private sono accessibili solo dall'interno della classe stessa. In una class, i membri sono privati per impostazione predefinita; in una struct sono pubblici. Tenere privati i dati e fornire metodi pubblici per accedervi è la base dell'incapsulamento, che protegge lo stato interno dell'oggetto da modifiche scorrette.

class BankAccount {
private:
    double balance = 0.0;   // Stato interno protetto

public:
    void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    double getBalance() const {
        return balance;
    }
};

Nel metodo getBalance compare la parola chiave const dopo la lista dei parametri: indica che il metodo non modifica lo stato dell'oggetto. È buona pratica marcare in questo modo tutti i metodi che si limitano a leggere i dati, perché documenta l'intento e consente al compilatore di applicare ulteriori controlli.

Costruttori

Un costruttore è un metodo speciale che viene invocato automaticamente al momento della creazione di un oggetto, con il compito di inizializzarne lo stato. Ha lo stesso nome della classe e non possiede tipo di ritorno. Grazie ai costruttori è possibile garantire che ogni oggetto nasca in uno stato valido e coerente.

#include <iostream>
#include <string>

class Person {
private:
    std::string name;
    int age;

public:
    // Costruttore con lista di inizializzazione
    Person(const std::string& personName, int personAge)
        : name(personName), age(personAge) {
    }

    void introduce() const {
        std::cout << "Mi chiamo " << name
                  << " e ho " << age << " anni" << std::endl;
    }
};

int main() {
    Person user("Giulia", 28);
    user.introduce();
    return 0;
}

La parte che segue i due punti, prima del corpo del costruttore, è la lista di inizializzazione dei membri: è il modo idiomatico e più efficiente di inizializzare gli attributi di un oggetto. Quando si crea l'oggetto user, i valori passati vengono assegnati direttamente ai membri name e age.

Il distruttore

Complementare al costruttore è il distruttore, un metodo speciale che viene invocato automaticamente quando un oggetto termina la propria vita, ad esempio quando esce dall'ambito in cui è stato definito. Il distruttore ha lo stesso nome della classe preceduto dal carattere tilde e serve a liberare eventuali risorse acquisite dall'oggetto, come memoria o file aperti.

class Resource {
public:
    Resource() {
        std::cout << "Risorsa acquisita" << std::endl;
    }

    // Distruttore: rilascia le risorse
    ~Resource() {
        std::cout << "Risorsa rilasciata" << std::endl;
    }
};

La gestione automatica del ciclo di vita degli oggetti, con costruttori e distruttori, è uno dei tratti distintivi del C++ e sta alla base di tecniche avanzate per la gestione sicura delle risorse. Con questo articolo si chiude la nostra introduzione: abbiamo percorso il cammino dai primi programmi fino alla definizione di tipi di dato personalizzati. Da qui si aprono argomenti più avanzati come l'ereditarietà, il polimorfismo, i template e la libreria standard, che potrai esplorare per padroneggiare appieno il linguaggio.