Non capisco gli argomenti contro l'overloading dell'operatore [chiuso]

82

Ho appena letto uno degli articoli di Joel in cui dice:

In general, I have to admit that I’m a little bit scared of language features that hide things. When you see the code

i = j * 5;

… in C you know, at least, that j is being multiplied by five and the results stored in i.

But if you see that same snippet of code in C++, you don’t know anything. Nothing. The only way to know what’s really happening in C++ is to find out what types i and j are, something which might be declared somewhere altogether else. That’s because j might be of a type that has operator* overloaded and it does something terribly witty when you try to multiply it.

(Enfasi mia.) Paura delle caratteristiche linguistiche che nascondono le cose? Come puoi esserne spaventato? Non nasconde le cose (anche conosciute come astrazione ) una delle idee chiave della programmazione orientata agli oggetti? Ogni volta che chiami un metodo a.foo(b) , non hai idea di cosa potrebbe fare. Devi scoprire quali tipi sono a e b , qualcosa che potrebbe essere dichiarato da qualche altra parte. Quindi dovremmo eliminare la programmazione orientata agli oggetti, perché nasconde troppe cose dal programmatore?

E in che modo j * 5 è diverso da j.multiply(5) , che potresti dover scrivere in una lingua che non supporta l'overloading dell'operatore? Di nuovo, dovresti trovare il tipo di j e sbirciare all'interno del metodo multiply , perché ecco ed ecco, j potrebbe essere di un tipo che ha un metodo multiply che fa qualcosa di terribilmente arguto.

"Muahaha, sono un programmatore malvagio che denomina un metodo multiply , ma ciò che effettivamente fa è totalmente oscuro e non intuitivo e non ha assolutamente nulla a che fare con le cose che moltiplicano". È uno scenario da prendere in considerazione quando si progetta un linguaggio di programmazione? Quindi dobbiamo abbandonare gli identificatori dai linguaggi di programmazione sulla base del fatto che potrebbero essere fuorvianti!

Se vuoi sapere che cosa fa un metodo, puoi dare uno sguardo alla documentazione o dare un'occhiata all'interno dell'implementazione. L'overloading dell'operatore è solo zucchero sintattico e non vedo come cambierà il gioco.

Per favore mi illumini.

    
posta fredoverflow 10.12.2010 - 12:45
fonte

15 risposte

32

L'astrazione "nasconde" il codice in modo da non doverti preoccupare dei meccanismi interni e spesso così non puoi cambiarli, ma l'intenzione non era di impedirti di guardarla. Facciamo solo delle ipotesi sugli operatori e, come ha detto Joel, potrebbe essere ovunque. Avere una funzione di programmazione che richiede che tutti gli operatori sovraccaricati siano stabiliti in una posizione specifica può aiutare a trovarlo, ma non sono sicuro che lo renda più facile.

Non vedo creare * fare qualcosa che non assomigli molto alla moltiplicazione meglio di una funzione chiamata Get_Some_Data che cancella i dati.

    
risposta data 10.12.2010 - 13:36
fonte
18

IMHO, le funzionalità del linguaggio come l'overloading dell'operatore danno al programmatore più potenza. E, come tutti sappiamo, con un grande potere derivano grandi responsabilità. Le caratteristiche che ti danno più potere ti danno anche più modi di spararti ai piedi e, ovviamente, dovrebbero essere usate con giudizio.

Ad esempio, ha perfettamente senso sovraccaricare il + o l'operatore * per class Matrix o class Complex . Tutti sapranno immediatamente cosa significa. D'altra parte, per me il fatto che + significhi concatenazione di stringhe non è affatto ovvio, anche se Java lo fa come parte del linguaggio, e STL fa per std::string usando l'overloading dell'operatore.

Un altro buon esempio di quando il sovraccarico dell'operatore rende il codice più chiaro sono i puntatori intelligenti in C ++. Vuoi che i puntatori intelligenti si comportino come puntatori regolari il più possibile, quindi ha perfettamente senso sovraccaricare gli operatori unario * e -> .

In sostanza, il sovraccarico dell'operatore non è altro che un altro modo per nominare una funzione. E c'è una regola per le funzioni di denominazione: il nome deve essere descrittivo, rendendo immediatamente evidente ciò che fa la funzione. La stessa esatta regola si applica al sovraccarico dell'operatore.

    
risposta data 10.12.2010 - 16:16
fonte
9

In Haskell "+", "-", "*", "/" ecc sono solo funzioni (infix).

Dovresti nominare una funzione infissa "più" come in "4 più 2"? Perché no, se l'aggiunta è ciò che fa la tua funzione. Dovresti nominare la tua funzione "più" "+"? Perché no.

Penso che il problema con i cosiddetti "operatori" siano, che somigliano per lo più a operazioni matematiche e non ci sono molti modi per interpretarli e quindi ci sono grandi aspettative su cosa faccia un tale metodo / funzione / operatore.

EDIT: reso più chiaro il mio punto

    
risposta data 10.12.2010 - 13:18
fonte
7

Sulla base delle altre risposte che ho visto, posso solo concludere che la vera obiezione al sovraccarico dell'operatore è il desiderio di un codice immediatamente ovvio.

Questo è tragico per due motivi:

  1. Portato alla sua logica conclusione, il principio che il codice dovrebbe essere immediatamente ovvio ci farebbe tutti codificare ancora in COBOL.
  2. Non si impara dal codice che è immediatamente evidente. Impari dal codice che ha senso quando prendi del tempo per pensare a come funziona.
risposta data 10.12.2010 - 17:06
fonte
5

Sono un po 'd'accordo.

Se scrivi multiply(j,5) , j potrebbe essere di tipo scalare o matriciale, rendendo multiply() più o meno complesso, a seconda di cosa sia j . Tuttavia, se si abbandona l'idea di sovraccaricare del tutto, allora la funzione dovrebbe essere chiamata multiply_scalar() o multiply_matrix() che renderebbe evidente ciò che sta accadendo sotto.

C'è un codice in cui molti di noi preferirebbero questo in un modo e c'è il codice in cui la maggior parte di noi preferirebbe l'altro modo. La maggior parte del codice, tuttavia, cade nel mezzo tra questi due estremi. Ciò che preferisci lì dipende dal tuo background e dalle tue preferenze personali.

    
risposta data 10.12.2010 - 13:15
fonte
4

Vedo due problemi con l'overloading dell'operatore.

  1. Il sovraccarico cambia la semantica dell'operatore, anche se non è previsto dal programmatore. Ad esempio, quando sovraccarichi && , || o , , perdi i punti di sequenza impliciti dalle varianti incorporate di questi operatori (oltre al comportamento di cortocircuito degli operatori logici). Per questo motivo, è meglio non sovraccaricare questi operatori, anche se la lingua lo consente.
  2. Alcune persone vedono il sovraccarico dell'operatore come una caratteristica così piacevole, iniziano a usarlo ovunque, anche se non è la soluzione appropriata. Questo fa sì che le altre persone reagiscano in modo eccessivo nella direzione opposta e mettono in guardia contro l'uso del sovraccarico dell'operatore. Non sono d'accordo con nessuno dei due gruppi, ma prendo la via di mezzo: il sovraccarico dell'operatore dovrebbe essere usato con parsimonia e solo quando
    • l'operatore sovraccarico ha il significato naturale sia per gli esperti di dominio che per gli esperti di software. Se quei due gruppi non sono d'accordo sul significato naturale per l'operatore, non sovraccaricarlo.
    • per i tipi coinvolti, non vi è alcun significato naturale per l'operatore e il contesto immediato (preferibilmente stessa espressione, ma non più di poche righe) rende sempre chiaro quale sia il significato dell'operatore. Un esempio di questa categoria sarebbe operator<< per i flussi.
risposta data 10.12.2010 - 16:02
fonte
3

Sulla base della mia esperienza personale, il modo Java di consentire più metodi, ma non di sovraccaricare l'operatore, significa che ogni volta che vedi un operatore conosci esattamente che cosa fa.

Non devi vedere se * richiama codice strano ma sappi che è un multiplo e si comporta esattamente come nel modo definito dalla specifica del linguaggio Java. Ciò significa che puoi concentrarti sul comportamento effettivo invece di scoprire tutte le cose wicket definite dal programmatore.

In altre parole, proibire il sovraccarico dell'operatore è un vantaggio per il lettore , non per lo writer , e quindi rende i programmi più facili da mantenere!

    
risposta data 10.12.2010 - 13:20
fonte
3

Una differenza tra il sovraccarico di a * b e la chiamata di multiply(a,b) è che quest'ultimo può essere facilmente sfruttato. Se la funzione multiply non è sovraccaricata per tipi diversi, puoi scoprire esattamente cosa farà la funzione, senza dover tracciare i tipi di a e b .

Linus Torvalds ha un argomento interessante sul sovraccarico dell'operatore. In qualcosa come lo sviluppo del kernel linux, in cui la maggior parte delle modifiche viene inviata tramite patch via email, è importante che i manutentori possano capire cosa farà una patch con solo poche righe di contesto attorno a ciascuna modifica. Se le funzioni e gli operatori non sono sovraccarichi, la patch può essere più facilmente letta in un modo indipendente dal contesto, poiché non è necessario passare attraverso il file modificato per capire quali sono tutti i tipi e controllare gli operatori sovraccaricati.

    
risposta data 10.12.2010 - 14:19
fonte
2

Sospetto che abbia qualcosa a che fare con le aspettative di rottura. Sono abituato al C ++, sei abituato al comportamento dell'operatore non dettato interamente dalla lingua, e non sarai sorpreso quando un operatore fa qualcosa di strano. Se sei abituato a lingue che non hanno questa caratteristica, e poi vedi il codice C ++, porti con sé le aspettative di quelle altre lingue e potresti essere sorprendentemente sorpreso quando scopri che un operatore sovraccarico fa qualcosa di funky.

Personalmente penso che ci sia una differenza. Quando è possibile modificare il comportamento della sintassi incorporata nella lingua, diventa più opaco ragionare. Le lingue che non consentono la meta-programmazione sono sintatticamente meno potenti, ma concettualmente più semplici da comprendere.

    
risposta data 10.12.2010 - 12:56
fonte
2

Penso che sovraccaricare gli operatori matematici non sia il vero problema con l'overloading dell'operatore in C ++. Penso che sovraccaricare operatori che non dovrebbero fare affidamento sul contesto dell'espressione (cioè il tipo) è "malvagio". Per esempio. sovraccarico , [ ] ( ) -> ->* new delete o anche unario * . Hai un certo insieme di aspettative da quegli operatori che non dovrebbero mai cambiare.

    
risposta data 10.12.2010 - 13:46
fonte
2

Capisco perfettamente che non ti piace la discussione di Joel sul nascondimento. Neanche io. È davvero molto meglio usare '+' per cose come i tipi numerici incorporati o per i propri come, ad esempio, la matrice. Ammetto che questo è pulito ed elegante per poter moltiplicare due matrici con '*' invece di '.multiply ()'. E dopotutto abbiamo lo stesso tipo di astrazione in entrambi i casi.

Ciò che fa male qui è la leggibilità del tuo codice. In casi reali, non nell'esempio accademico di moltiplicazione della matrice. Soprattutto se la tua lingua consente di definire operatori che non sono inizialmente presenti nel core della lingua, ad esempio =:= . A questo punto sorgono molte altre domande. Di cosa si occupa quel maledetto operatore? Intendo qual è la precedenza di quella cosa? Qual è l'associatività? In quale ordine è effettivamente eseguito a =:= b =:= c ?

Questo è già un argomento contro l'overloading dell'operatore. Non sei ancora convinto? Il controllo delle regole di precedenza non ti ha richiesto più di 10 secondi? Ok, andiamo oltre.

Se inizi a utilizzare un linguaggio che consente il sovraccarico dell'operatore, ad esempio quello popolare il cui nome inizia con "S", imparerai rapidamente che i progettisti di librerie amano ignorare gli operatori. Ovviamente sono ben istruiti, seguono le best practice (nessun cinismo qui) e tutte le loro API hanno perfettamente senso quando le guardiamo separatamente.

Ora immagina di dover utilizzare alcune API che fanno un uso massiccio degli operatori che sovraccaricano insieme in un unico pezzo di codice. O ancora meglio: devi leggere qualche codice legacy come quello. Questo è quando il sovraccarico dell'operatore fa davvero schifo. Fondamentalmente se ci sono molti operatori sovraccaricati in un posto, presto inizieranno a mescolarsi con gli altri caratteri non alfa-numerici nel codice del programma. Si mescoleranno con caratteri non alfanumerici che non sono realmente operatori ma piuttosto alcuni elementi grammaticali di una lingua più fondamentale che definiscono cose come blocchi e ambiti, che modellano le dichiarazioni di controllo del flusso o denotano alcune meta-cose. Avrai bisogno di mettere gli occhiali e spostare gli occhi di 10 cm più vicino al display LCD per capire che confusione visiva.

    
risposta data 16.04.2015 - 19:29
fonte
1

In generale, evito di usare l'overloading dell'operatore in modi non intuitivi. Cioè, se ho una classe numerica, il sovraccarico * è accettabile (e incoraggiato). Tuttavia, se ho un dipendente di classe, cosa farebbe l'overloading *? In altre parole, sovraccaricare gli operatori in modi intuitivi che semplificano la lettura e la comprensione.

Accettabile / Incoraggiato:

class Complex
{
public:
    double r;
    double i;

    Complex operator*(const Compex& rhs)
    {
        Complex result;
        result.r = (r * rhs.r) - (i * rhs.i);
        result.i = (r * rhs.i) + (i * rhs.r);
        return result;
    }
};

Non accettabile:

class Employee
{
public:
    std::string name;
    std::string address;
    std::string phone_number;

    Employee operator* (const Employee& e)
    {
        // what the hell do I do here??
    }
};
    
risposta data 10.12.2010 - 21:26
fonte
1

Oltre a ciò che è già stato detto qui, c'è un altro argomento contro l'overloading dell'operatore. Infatti, se scrivi + , è ovvio che tu intenda l'aggiunta di qualcosa a qualcosa. Ma questo non è sempre il caso.

Lo stesso C ++ fornisce un ottimo esempio di questo caso. Come si suppone che venga letto stream << 1 ? flusso spostato a sinistra di 1? Non è affatto ovvio a meno che tu non sappia esplicitamente che < < in C ++ scrive anche nel flusso. Tuttavia, se questa operazione fosse implementata come metodo, nessuno sviluppatore sano scriverà o.leftShift(1) , sarebbe qualcosa come o.write(1) .

La linea di fondo è che rendendo il sovraccarico dell'operatore non disponibile, il linguaggio fa pensare ai programmatori sui nomi delle operazioni. Anche se il nome scelto non è perfetto, è ancora più difficile interpretare erroneamente un nome rispetto a un segno.

    
risposta data 08.10.2011 - 14:04
fonte
1

Rispetto ai metodi enunciati, gli operatori sono più brevi, ma non richiedono nemmeno parentesi. Le parentesi sono relativamente scomode da digitare. E tu devi bilanciarli. In totale, qualsiasi chiamata al metodo richiede tre caratteri di rumore normale rispetto a un operatore. Questo rende l'utilizzo degli operatori molto, molto allettante.
Perché altrimenti qualcuno vorrebbe questo: cout << "Hello world" ?

Il problema con il sovraccarico è che la maggior parte dei programmatori è incredibilmente pigra e la maggior parte dei programmatori non può permettersi di esserlo.

Ciò che spinge i programmatori C ++ all'abuso dell'overloading dell'operatore non è la sua presenza, ma l'assenza di un modo più ordinato per eseguire chiamate di metodo. E le persone non hanno solo paura del sovraccarico dell'operatore perché è possibile, ma perché è fatto.
Si noti che ad esempio in Ruby e Scala nessuno teme il sovraccarico dell'operatore. A parte il fatto, che l'uso degli operatori non è molto più breve dei metodi, un'altra ragione è che Ruby limita l'overloading dell'operatore ad un minimo ragionevole, mentre Scala ti consente di dichiarare i tuoi operatori , evitando così di evitare collisioni banali.

    
risposta data 08.10.2011 - 23:08
fonte
0

Il motivo per cui l'overloading dell'operatore è spaventoso, è perché c'è un gran numero di programmatori che non avrebbero mai nemmeno pensato che * non significasse semplicemente "moltiplicare", mentre un metodo come foo.multiply(bar) almeno immediatamente punta a quel programmatore che qualcuno ha scritto un metodo personalizzato di moltiplicazione. A quel punto si chiederebbero perché e andranno a indagare.

Ho lavorato con "bravi programmatori" che erano in posizioni di alto livello che avrebbero creato metodi chiamati "CompareValues" che avrebbero preso 2 argomenti e applicato i valori da uno all'altro e restituire un valore booleano. O un metodo chiamato "LoadTheValues" che andrebbe nel database per altri 3 oggetti, ottenere valori, fare calcoli, modificare this e salvarlo nel database.

Se sto lavorando a un team con questi tipi di programmatori, so immediatamente di indagare su ciò su cui hanno lavorato. Se hanno sovraccaricato un operatore, non ho assolutamente modo di sapere che l'hanno fatto eccetto per presumere che l'abbiano fatto e andare a cercare.

In un mondo perfetto, o una squadra con programmatori perfetti, il sovraccarico dell'operatore è probabilmente uno strumento fantastico. Devo ancora lavorare su un team di programmatori perfetti, quindi è per questo che è spaventoso.

    
risposta data 08.10.2011 - 22:28
fonte

Leggi altre domande sui tag