La risposta accettata da Michael Burr è abbastanza buona per spiegare la tecnica, ma dai commenti sembra che oltre al "come" sei interessato al "perché". I motivi principali per fornire un sovraccarico dell'operatore per un determinato tipo migliorano la leggibilità e forniscono un'interfaccia richiesta.
Se si dispone di un tipo per il quale v'è un unico comunemente inteso nel senso di un operatore nel dominio del problema, quindi a condizione che come un sovraccarico operatore rende il codice più leggibile:
std::complex<double> a(1,2), b(3,4), c(5, 6);
std::complex<double> d = a + b + c; // compare to d = a.add(b).add(c);
std::complex<double> e = (a + d) + (b + c); // e = a.add(d).add(b.add(c));
Se il tipo di ha una determinata proprietà che sarà espressa naturalmente con un operatore, puoi sovraccaricare quel particolare operatore per il tuo tipo. Si consideri, ad esempio, che si desidera confrontare i propri oggetti per l'uguaglianza. Fornire operator==
(e operator!=
) può darti un modo semplice e leggibile di farlo.Questo ha il vantaggio di adempiere un'interfaccia comune che può essere utilizzato con algoritmi che dipendono uguaglianza:
struct type {
type(int x) : value(x) {}
int value;
};
bool operator==(type const & lhs, type const & rhs)
{ return lhs.value == rhs.value; }
bool operator!=(type const & lhs, type const & rhs)
{ return !lhs == rhs; }
std::vector<type> getObjects(); // creates and fills a vector
int main() {
std::vector<type> objects = getObjects();
type t(5);
std::find(objects.begin(), objects.end(), t);
}
noti che quando viene implementato l'algoritmo find
, dipende ==
essendo definita. L'implementazione di find funzionerà con i tipi primitivi e con qualsiasi tipo definito dall'utente con un operatore di uguaglianza definito. C'è un'interfaccia singola comune che ha senso. Confrontalo con la versione Java, dove il confronto dei tipi di oggetto deve essere eseguito tramite la funzione membro .equals
, mentre il confronto dei tipi primitivi può essere fatto con ==
. Consentendo di sovraccaricare gli operatori, è possibile lavorare con i tipi definiti dall'utente nello stesso modo in cui è possibile con i tipi primitivi.
Lo stesso vale per l'ordinazione. Se esiste un ordine (parziale) ben definito nel dominio della classe, fornire operator<
è un modo semplice per implementare quell'ordine. Codice sarà leggibile, e il tipo sarà utilizzabile in tutte le situazioni in cui è richiesto un ordine parziale, come all'interno di contenitori associativi:
bool operator<(type const & lhs, type const & rhs)
{
return lhs < rhs;
}
std::map<type, int> m; // m will use the natural `operator<` order
Un errore comune quando l'overloading degli operatori è stato introdotto nel linguaggio è quello del 'golden hammer "Una volta che hai un martello d'oro, tutto sembra un chiodo e il sovraccarico dell'operatore è stato abusato.
È importante notare che il motivo del sovraccarico in primo luogo sta migliorando la leggibilità. La leggibilità è migliorata solo se quando un programmatore guarda il codice, le intenzioni di ogni operazione sono chiare a prima vista, senza dover leggere le definizioni. Quando vedi che due numeri complessi vengono aggiunti come a + b
, sai cosa sta facendo il codice. Se la definizione dell'operatore non è naturale (si decide di implementarla aggiungendo solo la parte reale di essa), il codice diventerà più difficile da leggere rispetto a se fosse stata fornita una funzione (membro). Se il significato dell'operazione non è ben definito per il tipo lo stesso accade:
MyVector a, b;
MyVector c = a + b;
Che cosa è c
? È un vettore in cui ogni elemento i
è la somma dei rispettivi elementi da a
e b
oppure è un vettore creato concatenando gli elementi di a
prima degli elementi di b
. Per capire il codice, si avrebbe bisogno di andare alla definizione dell'operazione, e questo significa che il sovraccarico l'operatore è meno leggibile che fornire una funzione:
MyVector c = append(a, b);
L'insieme di operatori che possono essere sovraccaricato non è limitato agli operatori aritmetici e relazionali. È possibile sovraccaricare operator[]
per indicizzare in un tipo o operator()
per creare un callable oggetto che può essere usato come una funzione (questi sono chiamati funtori) o che semplificherà l'utilizzo della classe:
class vector {
public:
int operator[](int);
};
vector v;
std::cout << v[0] << std::endl;
class matrix {
public:
int operator()(int row, int column);
// operator[] cannot be overloaded with more than 1 argument
};
matrix m;
std::cout << m(3,4) << std::endl;
Ci sono altri usi dell'overloading dell'operatore. In particolare, lo operator,
può essere sovraccaricato in modi davvero fantasiosi per scopi di metaprogrammazione, ma probabilmente è molto più complesso di quello che ti interessa davvero ora.
+1 per gli operatori che non è possibile sovraccaricare. – FrustratedWithFormsDesigner
Grazie per l'assistenza ... purtroppo capisco meglio gli operatori sovraccaricati, la mia comprensione su come usarli non è ancora abbastanza vicino da permettermi di usarli. Ma almeno ora ho una migliore comprensione, grazie ancora! – Jeff
+1 Risposta fantastica. In realtà non mi è mai venuto in mente che l'uso di '<<' and '>>' per lo streaming era in realtà dovuto all'overloading dell'operatore nella libreria standard. Ho sempre pensato che fosse solo una di quelle stranezze del C++ che questi operatori intendevano solo cose diverse a seconda del contesto. – LeopardSkinPillBoxHat