Cerca nel sito:
ricerca
avanzata

Frasi Celebri...

La pittura ? poesia silenziosa, la poesia ? pittura che parla.

Simonide 

Sondaggio:

Windows XP ?...

Un grande affare
Il Sistema Operativo migliore sul mercato
Bello, ma niente di eccezionale
L'ennesima fregatura Microsoft
Una ciofeca peggiore dei suoi predecessori
Preferisco non commentare...

visualizza risultati


 

Le prime regole

 

Cosi` come la definizione di classe deve soddisfare precise regole sintattiche e semantiche, cosi` l'overloading di un operatore deve soddisfare un opportuno insieme di requisiti:

  1. Non e` possibile definire nuovi operatori, si puo` solamente eseguire l'overloading di uno per cui esiste gia` un simbolo nel linguaggio. Possiamo ad esempio definire un nuovo operatore *, ma non possiamo definire un operatore **.
    Questa regola ha lo scopo di prevenire possibili ambiguita`.
  2. Non e` possibile modificare la precedenza di un operatore e non e` possibile modificarne l'arieta` o l'associativita`, un operatore unario rimarra`sempre unario, uno binario dovra` applicarsi sempre a due operandi; analogamente uno associativo a sinistra rimmarra sempre associativo a sinistra.
  3. Non e` concessa la possibilita` di eseguire l'overloading di alcuni operatori, ad esempio l'operatore ternario ? :, l'operatore sizeof e gli operatori di cast e in particolare l'operatore .* e l'operatore punto (per la selezione dei campi di una struttura).
  4. E` possibile ridefinire un operatore sia come funzione globale che come funzione membro, i seguenti operatori devono tuttavia essere sempre funzioni membro non statiche: operatore di assegnamento ( = ), operatore di sottoscrizione ( [ ] ) e l'operatore ->.
A parte queste poche restrizioni non esistono molti altri limiti, possiamo ridefinire anche l'operatore virgola ( , ) e persino l'operatore chiamata di funzione ( () ); inoltre non c'e` alcuna restrizione riguardo il contenuto del corpo di un operatore: un operatore altro non e` che un tipo particolare di funzione e tutto cio` che puo` essere fatto in una funzione puo` essere fatto anche in un operatore.
Un operatore e` indicato dalla keyword operator seguita dal simbolo dell'operatore, per eseguirne l'overloading come funzione globale bisogna utilizzare la seguente sintassi:
  < ReturnType > operator@( < ArgumentList > ) { < Body > }
ReturnType e` il tipo restituito (non ci sono restrizioni); @ indica un qualsiasi simbolo di operatore valido; ArgumentList e` la lista di parametri (tipo e nome) che l'operatore riceve, i parametri sono due per un operatore binario (il primo e` quello che compare a sinistra dell'operatore quando esso viene applicato) mentre e` uno solo per un operatore unario. Infine Body e` la sequenza di istruzioni che costituiscono il corpo dell'operatore.
Ecco un esempio di overloading di un operatore come funzione globale:


  struct Complex {
    float Re;
    float Im;
  };

  Complex operator+(const Complex& A, const Complex& B) {
    Complex Result;
    Result.Re = A.Re + B.Re;
    Result.Im = A.Im + B.Im;
    return Result;
  }


Si tratta sicuramente di un caso molto semplice, che fa capire che in fondo un operatore altro non e` che una funzione. Il funzionamento del codice e` chiaro e non mi dilunghero` oltre; si noti solo che i parametri sono passati per riferimento, non e` obligatorio, ma solitamente e` bene passare i parametri in questo modo (eventualmente utilizzando const come nell'esempio).
Definito l'operatore, e` possibile utilizzarlo secondo l'usuale sintassi riservata agli operatori, ovvero come nel seguente esempio:


  Complex A, B;
  /* ... */
  Complex C = A + B;


L'esempio richiede che sia definito su Complex il costruttore di copia, ma come gia` sapete il compilatore e` in grado di fornirne uno di default. Detto questo il precedente esempio viene tradotto (dal compilatore) in


  Complex A, B;
  /* ... */
  Complex C(operator+(A, B));


Volendo potete utilizzare gli operatori come funzioni, esattamente come li traduce il compilatore (cioe` scrivendo Complex C = operator+(A, B) o Complex C(operator+(A, B))), ma non e` una buona pratica in quanto annulla il vantaggio ottenuto ridefinendo l'operatore.
Quando un operatore viene ridefinito come funzione membro il primo parametro e` sempre l'istanza della classe su cui viene eseguito e non bisogna indicarlo nella lista di argomenti, un operatore binario quindi come funzione globale riceve due parametri ma come funzione membro ne riceve solo uno (il secondo operando); analogamente un operatore unario come funzione globale prende un solo argomento, ma come funzione membro ha la lista di argomenti vuota.
Riprendiamo il nostro esempio di prima ampliandolo con nuovi operatori:


  class Complex {
    public:
      Complex(float re, float im);
      Complex operator-() const;    // - unario
      Complex operator+(const Complex& B) const;
      const Complex & operator=(const Complex& B);

    private:
      float Re;
      float Im;
  };

  Complex::Complex(float re, float im = 0.0) {
    Re = re;
    Im = im;
  }

  Complex Complex::operator-() const {
    return Complex(-Re, -Im);
  }

  Complex Complex::operator+(const Complex& B) const {
    return Complex(Re+B.Re, Im+B.Im);
  }

  const Complex& Complex::operator=(const Complex& B) {
    Re = B.Re;
    Im = B.Im;
    return B;
  }


La classe Complex ridefinisce tre operatori. Il primo e` il -(meno) unario, il compilatore capisce che si tratta del meno unario dalla lista di argomenti vuota, il meno binario invece, come funzione membro, deve avere un parametro. Successivamente viene ridefinito l'operatore + (somma), si noti la differenza rispetto alla versione globale. Infine viene ridefinito l'operatore di assegnamento che come detto sopra deve essere una funzione membro non statica; si noti che a differenza dei primi due questo operatore ritorna un riferimento, in tal modo possiamo concatenare piu` assegnamenti evitando la creazione di inutili temporanei, l'uso di const assicura che il risultato non venga utilizzato per modificare l'oggetto. Infine, altra osservazione, l'ultimo operatore non e` dichiarato const in quanto modifica l'oggetto su cui e` applicato (quello cui si assegna), se la semantica che volete attribuirgli consente di dichiararlo const fatelo, ma nel caso dell'operatore di assegnamento (e in generale di tutti) e` consigliabile mantenere la coerenza semantica (cioe` ridefinirlo sempre come operatore di assegnamento, e non ad esempio come operatore di uguaglianza).
Ecco alcuni esempi di applicazione dei precedenti operatori e la loro rispettiva traduzione in chiamate di funzioni (A, B e C sono variabili di tipo Complex):


  B = -A;       // B.operator=(A.operator-());
  C = A+B;      // C.operator=(A.operator+(B));
  C = A+(-B);   // C.operator=(A.operator+(B.operator-()))
  C = A-B;      // errore!
                // complex& Complex::operator-(Complex&)
                // non definito.


L'ultimo esempio e` errato poiche` quello che si vuole utilizzare e` il meno binario, e tale operatore non e` stato definito.
Passiamo ora ad esaminare con maggiore dettaglio alcuni operatori che solitamente svolgono ruoli piu` difficili da capire.

 

successivo
–«  INDICE  »–

 

 

 

 
Powered by paper&pencil (carta&matita ) - Copyright © 2001-2019 Cataldo Sasso