programmazione
costruttori e distruttori Costruttori e distruttori rappresentano l'inizio e la fine del ciclo di vita di un oggetto. Il costruttore prepara l'oggetto all'uso, inizializzando i dati e acquisendo eventuali risorse necessarie; il distruttore, invece, si occupa della loro corretta liberazione.
         home page  
 

Costruttori e distruttori nelle classi: come funzionano davvero.

Pubblicato il: 22-7-2026

Ultima Modifica: 22-7-2026

📖 Introduzione

Quando si inizia a programmare con le classi, una delle prime domande che ci si pone è:

"Quando viene creato un oggetto?"

e subito dopo:

"Quando viene eliminato?"

La risposta è racchiusa in due strumenti fondamentali della programmazione orientata agli oggetti:

  • 🟢 Costruttore (Constructor)

  • 🔴 Distruttore (Destructor)

Sono due metodi speciali che il compilatore richiama automaticamente nei momenti più importanti della vita di un oggetto.

Comprendere bene il loro funzionamento significa evitare errori, perdite di memoria e programmi instabili.

In questo articolo vedremo come funzionano davvero, con numerosi esempi pratici.


🏠 Immaginiamo una casa...

Pensiamo ad una classe come al progetto di una casa.

Ogni volta che costruiamo una nuova casa:

  • vengono installate porte;

  • finestre;

  • impianti;

  • mobili.

Questa fase corrisponde al costruttore.

Quando invece la casa viene demolita bisogna:

  • togliere i mobili;

  • chiudere le utenze;

  • eliminare i materiali.

Questa è la funzione del distruttore.

Lo stesso accade agli oggetti creati da un programma.


🟢 Cos'è un costruttore?

Il costruttore è una funzione speciale che viene eseguita automaticamente quando viene creato un oggetto.

Il suo scopo principale è:

✅ inizializzare le variabili

✅ preparare l'oggetto all'uso

✅ allocare eventuale memoria

✅ aprire file

✅ creare connessioni


📌 Caratteristiche del costruttore

Un costruttore:

  • ha lo stesso nome della classe

  • non restituisce alcun valore

  • non possiede tipo di ritorno

  • viene richiamato automaticamente.

Sintassi:

  
class Persona
{
public:

    Persona()
    {
        // Costruttore
    }

};

Notare che non compare:

void

oppure

int

perché il costruttore non restituisce nulla.


👤 Primo esempio

  
#include <iostream>

using namespace std;

class Persona
{
public:

    Persona()
    {
        cout << "Oggetto creato." << endl;
    }

};

int main()
{
    Persona p;

    return 0;
}

Output

Oggetto creato.

Appena viene creato l'oggetto:

Persona p;

il costruttore viene eseguito automaticamente.

Non serve chiamarlo.


⚙️ Inizializzare le variabili

Uno degli usi più comuni consiste nell'assegnare valori iniziali.

  
#include <iostream>

using namespace std;

class Auto
{
public:

    string modello;
    int anno;

    Auto()
    {
        modello = "Sconosciuto";
        anno = 2025;
    }
};

int main()
{
    Auto a;

    cout << a.modello << endl;
    cout << a.anno << endl;
}

Output

Sconosciuto
2025

L'oggetto nasce già inizializzato.


🎯 Costruttori con parametri

Molto spesso vogliamo creare oggetti diversi.

Esempio:

  
Persona p1("Mario");

Persona p2("Luca");

Persona p3("Anna");

Per fare questo si usano i costruttori parametrizzati.

  
#include <iostream>

using namespace std;

class Persona
{
public:

    string nome;

    Persona(string n)
    {
        nome = n;
    }

};

int main()
{
    Persona p("Mario");

    cout << p.nome;
}

Output

Mario

📦 Più parametri

  
class Persona
{
public:

    string nome;
    int eta;

    Persona(string n, int e)
    {
        nome = n;
        eta = e;
    }

};

Creazione:

  
Persona p("Marco",35);

✨ Costruttori multipli (Overloading)

Una classe può avere più costruttori.

  
class Rettangolo
{
public:

    int base;
    int altezza;

    Rettangolo()
    {
        base = 1;
        altezza = 1;
    }

    Rettangolo(int b,int h)
    {
        base = b;
        altezza = h;
    }

};

Utilizzo

  
Rettangolo r1;

Rettangolo r2(20,15);

📋 Costruttore di copia

Uno dei costruttori più importanti è il Copy Constructor.

Serve a creare un oggetto copiandone un altro.

  
class Persona
{
public:

    string nome;

    Persona(string n)
    {
        nome = n;
    }

    Persona(const Persona &p)
    {
        nome = p.nome;
    }

};

Utilizzo

  
Persona p1("Paolo");

Persona p2 = p1;

Il secondo oggetto riceve una copia del primo.


🚀 Lista di inizializzazione

In C++ moderno è preferibile inizializzare i membri direttamente nella lista di inizializzazione.

  
class Persona
{
public:

    string nome;
    int eta;

    Persona(string n,int e)
        : nome(n), eta(e)
    {

    }

};

Questa tecnica è:

✔ più veloce

✔ più elegante

✔ spesso obbligatoria con oggetti costanti o riferimenti.


🔴 Cos'è un distruttore?

Il distruttore è il contrario del costruttore.

Viene eseguito automaticamente quando l'oggetto termina la propria esistenza.

Serve a:

🗑 liberare memoria

📁 chiudere file

🌐 chiudere connessioni

🔓 liberare risorse


📌 Sintassi

  
class Persona
{
public:

    ~Persona()
    {

    }

};

Si riconosce dalla tilde:

~

che precede il nome della classe.


👀 Primo esempio

  
#include <iostream>

using namespace std;

class Persona
{
public:

    Persona()
    {
        cout << "Creata" << endl;
    }

    ~Persona()
    {
        cout << "Distrutta" << endl;
    }

};

int main()
{
    Persona p;

    cout << "Programma in esecuzione" << endl;

    return 0;
}

Output

Creata

Programma in esecuzione

Distrutta

Il distruttore viene richiamato automaticamente al termine del programma.


📂 Chiudere un file

Immaginiamo di aprire un file nel costruttore.

  
class FileManager
{
private:

    FILE *file;

public:

    FileManager()
    {
        file = fopen("dati.txt","r");
    }

    ~FileManager()
    {
        if(file!=NULL)
            fclose(file);
    }

};

In questo modo il file viene sempre chiuso.


💾 Liberare memoria

Uno degli usi più importanti riguarda la memoria dinamica.

  
class Vettore
{
private:

    int *numeri;

public:

    Vettore()
    {
        numeri = new int[100];
    }

    ~Vettore()
    {
        delete[] numeri;
    }

};

Senza il distruttore si avrebbe una memory leak.


⚠️ Memory Leak

Una perdita di memoria si verifica quando:

  • viene allocata memoria

  • non viene più liberata.

Esempio errato

  
int *p = new int[100];

Fine.

Mai eseguito:

  
delete[] p;

La memoria rimane occupata.


📌 Ordine di chiamata

Quando viene creato un oggetto:

Costruttore

Quando termina:

Distruttore

Con più oggetti:

Persona a;
Persona b;
Persona c;

L'ordine sarà:

Creazione

a

b

c

Distruzione

c

b

a

È l'ordine inverso.


🧱 Oggetti locali

  
void funzione()
{
    Persona p;
}

Alla fine della funzione:

viene chiamato il distruttore

automaticamente.


🌍 Oggetti globali

Gli oggetti globali vengono distrutti solo alla chiusura del programma.


🧠 Costruttori e distruttori nelle gerarchie di classi

Supponiamo di avere una classe base e una derivata.

  
class Animale
{
public:

    Animale()
    {
        cout << "Animale creato\n";
    }

    ~Animale()
    {
        cout << "Animale distrutto\n";
    }

};

class Cane : public Animale
{
public:

    Cane()
    {
        cout << "Cane creato\n";
    }

    ~Cane()
    {
        cout << "Cane distrutto\n";
    }

};

Creando:

Cane c;

si ottiene:

Animale creato

Cane creato

Cane distrutto

Animale distrutto

Prima vengono costruite le classi base, poi quelle derivate. Durante la distruzione avviene l'opposto.


⚠️ Distruttori virtuali

Quando una classe è pensata per essere usata come classe base e gli oggetti possono essere eliminati tramite un puntatore alla classe base, il distruttore dovrebbe essere dichiarato virtual.

  
class Animale
{
public:
    virtual ~Animale()
    {
        cout << "Distruttore Animale" << endl;
    }
};

class Cane : public Animale
{
public:
    ~Cane()
    {
        cout << "Distruttore Cane" << endl;
    }
};

int main()
{
    Animale* a = new Cane();

    delete a;
}

Output:

Distruttore Cane
Distruttore Animale

Senza il distruttore virtuale, il distruttore della classe derivata potrebbe non essere eseguito, causando perdite di risorse.


⚠️ Errori comuni

❌ Dimenticare di liberare la memoria

  
puntatore = new int[100];

ma mai

  
delete[] puntatore;

❌ Inserire logica complessa nel distruttore

Il distruttore dovrebbe limitarsi alla pulizia delle risorse. Operazioni lunghe o che possono generare eccezioni sono generalmente da evitare.


❌ Inizializzare i membri nel corpo del costruttore quando è preferibile la lista di inizializzazione

Meglio:

  
Persona(string n) : nome(n)
{
}

che:

Persona(string n)
{
    nome = n;
}

💡 Buone pratiche

✔️ Inizializzare sempre gli oggetti.

✔️ Usare la lista di inizializzazione quando possibile.

✔️ Liberare tutte le risorse acquisite.

✔️ Dichiarare un distruttore virtual nelle classi base polimorfiche.

✔️ Preferire, quando possibile, contenitori e smart pointer della libreria standard (std::vector, std::unique_ptr, std::shared_ptr) per ridurre la gestione manuale della memoria.


📚 In sintesi

Costruttori e distruttori rappresentano l'inizio e la fine del ciclo di vita di un oggetto. Il costruttore prepara l'oggetto all'uso, inizializzando i dati e acquisendo eventuali risorse necessarie; il distruttore, invece, si occupa della loro corretta liberazione.

Capire quando vengono eseguiti, come utilizzare i costruttori parametrizzati, il costruttore di copia, la lista di inizializzazione e l'importanza dei distruttori virtuali permette di scrivere codice più sicuro, efficiente e facile da mantenere.

Una buona gestione del ciclo di vita degli oggetti è uno dei pilastri della programmazione orientata agli oggetti e costituisce la base per affrontare argomenti più avanzati, come il principio RAII (Resource Acquisition Is Initialization), gli smart pointer e la progettazione di librerie robuste e professionali.