Data una funzione come:
void do_stuff( Thing & thing )
{
// at this point, I can inadvertently or purposefully change thing
}
C'è una ragione per cambiare intenzionalmente qualcosa? Cambiare cosa è possibile ma è sconsiderato?
Data una funzione come:
void do_stuff( Thing & thing )
{
// at this point, I can inadvertently or purposefully change thing
}
C'è una ragione per cambiare intenzionalmente qualcosa? Cambiare cosa è possibile ma è sconsiderato?
Sì, quindi la funzione può cambiare thing
.
In generale, è meglio mettere il comportamento sull'oggetto stesso. In altre parole, preferisci questo:
class Thing {
public:
void do_stuff() {
...
}
};
... a questo:
void do_stuff( Thing & thing ) {
...
}
Tuttavia, entrambi gli esempi sono perfettamente validi e utilizzabili. Un buon esempio di parametro di riferimento non const non membro è rappresentato dagli operatori di estrazione stream. Questa domanda di overflow dello stack va più nel dettaglio: Qualcuno usa effettivamente gli operatori di estrazione del flusso? Il generale l'idea è questa:
operator>>(std::istream &, Thing &)
La funzione non può esistere come parte della classe Thing perché la mano sinistra non è un oggetto della classe. Deve anche modificare un oggetto Thing in modo che il riferimento non possa essere const.
Ci sono dei motivi per cui potresti voler cambiare le cose sul posto. Una ragione potrebbe essere la prestazione: è costoso creare tutti questi nuovi oggetti per l'output.
Ci sono dei motivi per cui potresti scrivere cose in uno stile più funzionale, immutabile . Una ragione è trasparenza referenziale un'altra è la combinazione di funzioni e concorrenza più semplice e più affidabile.
Tutto è un compromesso. Come per la maggior parte delle cose nello sviluppo del software, la scelta giusta dipende dalle vostre esigenze specifiche. C ++ è un linguaggio multi-paradigma; ti permette di fare la scelta da solo.
Sì, alcune volte è molto conveniente essere in grado di modificare localmente un argomento pass-by-value in una funzione. Ad esempio, l'argomento potrebbe essere un riferimento a un nodo di un elenco collegato e all'interno della funzione potresti voler attraversare l'elenco, quindi vorrai fare node = *(node.next);
.
Quindi, sconsiderato? sicuramente no, secondo me.
Esistono linguaggi che non ti permettono di modificare gli argomenti della funzione, (Pascal, penso?) che ti costringe a fare una copia dell'argomento in una variabile locale per modificarlo, e quindi il problema è che entrambi la copia e l'argomento originale sono in scope, quindi più in basso nel tuo codice potresti leggere erroneamente l'argomento al posto della variabile. Questo tipo di errore è probabilmente più probabile di una modifica involontaria dell'argomento. Quindi, consentendo all'argomento di cambiare, si elimina la variabile e quindi la possibilità di un tale errore. (Non mi preoccuperò nemmeno del problema di prestazioni, è trascurabile e persino ottimizzabile dal compilatore.)
Per quanto riguarda la possibilità di modificare erroneamente thing
, questo è quello che ho da dire:
Nella mia esperienza, circa il 90% delle variabili membro, i parametri e le variabili locali che dichiariamo sono effettivamente const
. ( final
in java, readonly
in C #.) Ciò significa che sebbene non li dichiariamo esplicitamente come const
, li trattiamo come const
: non cambiamo mai il loro valore dopo l'inizializzazione. Pertanto, spero che un linguaggio (* 1) venga creato un giorno in cui tutte le "variabili" saranno const
a meno che non sia dichiarato esplicitamente di non essere const
con l'uso di uno speciale "non -const "( mutable
forse?) Parola chiave. In questo modo, nel caso raro che una variabile sia prevista che cambi, sarà immediatamente evidente nella sua dichiarazione, e il compilatore sarà anche in grado di emettere un avvertimento se dichiarate qualcosa come mutevole e dimenticate di cambiarlo. Ciò, naturalmente, avrà anche il vantaggio di impedirti di modificare involontariamente il tuo thing
se non lo hai dichiarato come mutabile.
(* 1): una lingua imperativa , dal momento che si potrebbe sottolineare che i linguaggi funzionali già lo fanno.
Sì, come altri hanno già detto, da modificare.
Un motivo molto convincente per farlo è il polimorfismo. I riferimenti sono polimorfici, ma gli oggetti potrebbero slice .
Un altro è chiamare una funzione membro non const su un oggetto.
Un esempio:
class A
{
public:
virtual ~A() = default;
virtual void someMethod() { printf("A::someMethod()"); }
};
class Subclass: public A
{
public:
void someMethod() override { printf("Subclass::someMethod()"); }
};
//always prints "A::someMethod()" because the object is sliced
void do_stuff_wrong( A thing )
{
thing.someMethod();
}
void dont_do_stuff( const A & thing )
{
//thing.someMethod();// compilation failure.
}
// do stuff can legally call someMethod() which is not marked const
void do_stuff( A & thing )
{
// Yay! it calls out polymorphically
thing.someMethod();
}
In questo esempio
Leggi altre domande sui tag coding-style c++