Overloading degli Operatori in Linguaggio C++

Uno dei costrutti del C++ più potenti è la possibilità di ridefinire il significato di un operatore che interviene fra due classi, in questo articolo tratteremo dell’overloading degli operatori.
Gli operatori che è più utile ridefinire sono:

  • operatori di confronto come == ,<
  • operatori aritmetici come +, –
  • l’operatore [] per esprimere l’indirizzamento su un array
  • l’operatore () di chiamata di funzione
  • l’operatore di deferenziamento di puntatore ->
  • gli operatori << e >> per le letture o scritture su stream
  • gli operatori new e delete per gestire l’allocazione e disallocazione di memoria
  • gli operatori ++, — e di deferenziamento * unario

Per cominciare prenderemo in esame gli operatori più semplici da trattare, quelli aritmetici, rinviando ad un prossimo articolo quelli più complicati come gli smart pointer.
Ci sono due modi per definire un operatore: scrivere una funzione che ha come nome la keyword operator seguita dall’operatore stesso

class operator+(const class& c1, const class& c2)

oppure, ma solo quando il primo operando è un oggetto, tramite una funzione membro della classe di quell’oggetto

public:
class& operator+(const class& c)

per gli operatori unari è possibile utilizzare solo la seconda soluzione. Quando utilizzare la funzione globale o quella membro? Generalmente è consigliabile quando possibile quella globale perché si applicano ad entrambi tutte le conversioni di tipo definite dall’utente. Vediamo allora un semplice utilizzo dell’overloading degli operatori con una nostra classe per definire un punto su un asse cartesiano, chiamiamola “CMyPunto”.

// File Corso.h

class CMyPunto
{
private:

public:
   int x, y;

   CMyPunto(int x0, int y0) { x = x0; y = y0; };

   void operator+=(const CMyPunto& pt);

   void Stampa();
};

CMyPunto operator+(const CMyPunto& pt1, const CMyPunto& pt2);
bool operator==(const CMyPunto& pt1, const CMyPunto& pt2);

// File Corso.cpp
#include <iostream>
#include "Corso.h"

using namespace std;

void CMyPunto::Stampa()
{
   cout << "Il valore del punto" << endl;
   cout << "x: " << x << endl;
   cout << "y: " << y << endl << endl;
}

CMyPunto operator+(const CMyPunto& pt1, const CMyPunto& pt2)
{
   CMyPunto ptTemp(0, 0);
   ptTemp.x = pt1.x + pt2.x;
   ptTemp.y = pt1.y + pt2.y;
   return ptTemp;
}

void CMyPunto::operator+=(const CMyPunto& pt)
{
   x += pt.x;
   y += pt.y;
}

bool operator==(const CMyPunto& pt1, const CMyPunto& pt2)
{
   if (pt1.x == pt2.x && pt1.y == pt2.y)
      return true;

   return false;
}

int main(int argc, char** args)
{
   CMyPunto pt1(12, 7);
   CMyPunto pt2(24, 18);

   (pt1 + pt2).Stampa();
   pt1 += pt2;
   pt1.Stampa();

   if (pt1 == pt2)
       cout << "Sono uguali" << endl << endl;
   else
       cout << "Sono diversi" << endl << endl;

    int k;
    cin >> k;

   return 0;
}

In C++ c’è uno strano caso sul quale mi voglio soffermare prima di terminare questo articolo. Avete mai pensato alla diferrenza degli operatori unari di incremento ( ++ ) o di decremento ( — ) postfisso o prefisso? Ad esempio queste due righe sono uguali?

i++; // postfisso
++i; // prefisso

Vediamo con l’uso dell’overloading degli operatori le differenze di implementazione:

// File Corso.h

class CMyPunto
{
private:

public:
   int x, y;

   CMyPunto(int x0, int y0) { x = x0; y = y0; };

   CMyPunto& operator++(); // prefisso
   CMyPunto operator++(int); // postfisso

   void Stampa();
};

// File Corso.cpp

#include <iostream>
#include "Corso.h"

using namespace std;

void CMyPunto::Stampa()
{
   cout << "Il valore del punto" << endl;
   cout << "x: " << x << endl;
   cout << "y: " << y << endl << endl;
}

CMyPunto& CMyPunto::operator++()
{
   x++;
   y++;
   return *this;
}

CMyPunto CMyPunto::operator++(int)
{
    x++;
    y++;
    return *this;
 }

 int main(int argc, char** args)
 {
    CMyPunto pt(12, 7);
    pt++;
    pt.Stampa();
    ++pt;
    pt.Stampa();

    int k;
    cin >> k;

   return 0;
}

Per distinguere l’operatore prefisso da quello postfisso è necessario utilizzare una sintassi differente, infatti la versione normale degli operatori è quella prefissa che restituiscono un reference, la versione postfissa si differenzia per un parametro dummy di tipo int e restituisce una copia dell’oggetto. La strana sintassi è stata giustificata affermando che l’operatore unario postfisso può essere visto come un operatore binario il cui primo operando è di tipo Iterator ed il secondo, invisibile, è di tipo int. Da notare che la versione postfissa restituisce il risultato per valore, mentre la forma prefissa lo restituisce per reference, quindi questa seconda forma è più efficiente della prima. Da questo ragionamento conviene scrivere

for (int i = 0; i < 100; ++i)

piuttosto che

for (int i = 0; i < 100; i++)

In un prossimo articolo continueremo la trattazione dell’overloading degli operatori parlando di double dispatching ed allocazione di memoria.

Informazioni su Giampaolo Rossi

Sviluppatore di software gestionale da oltre 28 anni.
Questa voce è stata pubblicata in VC/C++. Contrassegna il permalink.