Vi sono alcune differenze nelle convenzioni di chiamata in C ++ e Java. In C ++ ci sono tecnicamente solo due convenzioni: pass-by-value e pass-by-reference, con alcune pubblicazioni tra cui una terza convenzione pass-by-pointer (che in realtà è pass-by-value di un tipo di puntatore). Inoltre, puoi aggiungere costanza al tipo di argomento, migliorando la semantica.
Passa per riferimento
Passando per riferimento significa che la funzione riceverà concettualmente la tua istanza di oggetto e non una sua copia. Il riferimento è concettualmente un alias per l'oggetto che è stato utilizzato nel contesto chiamante e non può essere nullo. Tutte le operazioni eseguite all'interno della funzione si applicano all'oggetto esterno alla funzione. Questa convenzione non è disponibile in Java o C.
Passa per valore (e pass-by-pointer)
Il compilatore genererà una copia dell'oggetto nel contesto chiamante e utilizzerà quella copia all'interno della funzione. Tutte le operazioni eseguite all'interno della funzione vengono eseguite sulla copia, non sull'elemento esterno. Questa è la convenzione per i tipi primitivi in Java.
Una versione speciale di esso passa un puntatore (indirizzo dell'oggetto) in una funzione. La funzione riceve il puntatore e tutte le operazioni applicate al puntatore stesso vengono applicate alla copia (puntatore), d'altra parte, le operazioni applicate al puntatore senza riferimenti verranno applicate all'istanza dell'oggetto in quella posizione di memoria, quindi la funzione può avere effetti collaterali. L'effetto dell'utilizzo del valore pass-by di un puntatore sull'oggetto consentirà alla funzione interna di modificare i valori esterni, come nel caso del riferimento pass-by e consentirà anche valori opzionali (passare un puntatore null).
Questa è la convenzione utilizzata in C quando una funzione deve modificare una variabile esterna e la convenzione utilizzata in Java con tipi di riferimento: il riferimento viene copiato, ma l'oggetto riferito è lo stesso: le modifiche al riferimento / puntatore non sono visibili all'esterno la funzione, ma sono le modifiche alla memoria appuntita.
Aggiungendo const all'equazione
In C ++ è possibile assegnare costanza agli oggetti quando si definiscono variabili, puntatori e riferimenti a diversi livelli. Puoi dichiarare una variabile come costante, puoi dichiarare un riferimento a un'istanza costante e puoi definire tutti i puntatori a oggetti costanti, puntatori costanti a oggetti mutabili e puntatori costanti a elementi costanti. Viceversa in Java è possibile definire solo un livello di costanza (parola chiave finale): quello della variabile (istanza per tipi primitivi, riferimento per tipi di riferimento), ma non è possibile definire un riferimento a un elemento immutabile (a meno che la classe stessa non sia immutabile).
Questo è ampiamente utilizzato nelle convenzioni di chiamata C ++. Quando gli oggetti sono piccoli, è possibile passare l'oggetto per valore. Il compilatore genererà una copia, ma quella copia non è un'operazione costosa. Per qualsiasi altro tipo, se la funzione non modificherà l'oggetto, è possibile passare un riferimento a un'istanza costante (in genere denominata riferimento costante) del tipo. Questo non copierà l'oggetto, ma lo passerà nella funzione. Allo stesso tempo, il compilatore garantirà che l'oggetto non venga modificato all'interno della funzione.
Regole pratiche
Queste sono alcune regole di base da seguire:
- Preferisce il valore pass-by per i tipi primitivi
- Preferisci il riferimento pass-by con riferimenti alla costante per altri tipi
- Se la funzione deve modificare l'argomento, utilizzare il pass-by-reference
- Se l'argomento è facoltativo, utilizzare il pass-by-pointer (per costante se il valore facoltativo non deve essere modificato)
Ci sono altre piccole deviazioni da queste regole, la prima delle quali è la gestione della proprietà di un oggetto. Quando un oggetto viene allocato dinamicamente con new, deve essere deallocato con delete (o le sue versioni []). L'oggetto o la funzione responsabile della distruzione dell'oggetto è considerato il proprietario della risorsa. Quando un oggetto allocato in modo dinamico viene creato in un pezzo di codice, ma la proprietà viene trasferita a un altro elemento, di solito viene eseguita con semantica pass-by-pointer o, se possibile, con smart pointer.
Nota a margine
È importante insistere sull'importanza della differenza tra riferimenti C ++ e Java. In C ++ i riferimenti sono concettualmente l'istanza dell'oggetto, non un accedente ad esso. L'esempio più semplice sta implementando una funzione di scambio:
// C++
class Type; // defined somewhere before, with the appropriate operations
void swap( Type & a, Type & b ) {
Type tmp = a;
a = b;
b = tmp;
}
int main() {
Type a, b;
Type old_a = a, old_b = b;
swap( a, b );
assert( a == old_b );
assert( b == old_a );
}
La funzione di scambio sopra cambia entrambi i suoi argomenti attraverso l'uso di riferimenti. Il codice più vicino in Java:
public class C {
// ...
public static void swap( C a, C b ) {
C tmp = a;
a = b;
b = tmp;
}
public static void main( String args[] ) {
C a = new C();
C b = new C();
C old_a = a;
C old_b = b;
swap( a, b );
// a and b remain unchanged a==old_a, and b==old_b
}
}
La versione Java del codice modificherà le copie dei riferimenti internamente, ma non modificherà gli oggetti reali esternamente. I riferimenti Java sono puntatori C senza aritmetica del puntatore che vengono passati per valore nelle funzioni.