Perché i parametri predefiniti devono essere aggiunti per ultimi nelle funzioni C ++?

Perché i parametri predefiniti devono essere aggiunti per ultimi nelle funzioni C ++?

Per semplificare la definizione della lingua e mantenere leggibile il codice.

void foo(int x = 2, int y); 

Per chiamarlo e approfittare del valore predefinito, avresti bisogno della syntax come questa:

 foo(, 3); 

Probabilmente si sentiva troppo strano. Un’altra alternativa è specificare i nomi nell’elenco degli argomenti:

 foo(y : 3); 

Dovrebbe essere usato un nuovo simbolo perché questo già significa qualcosa:

 foo(y = 3); // assign 3 to y and then pass y to foo. 

L’approccio di denominazione è stato preso in considerazione e respinto dal comitato ISO perché si sentiva a disagio nell’introdurre un nuovo significato per i nomi dei parametri al di fuori della definizione della funzione.

Se sei interessato a più razionali di design in C ++, leggi The Design ed Evolution of C ++ di Stroustrup.

Se si definisce la seguente funzione:

 void foo( int a, int b = 0, int c ); 

Come chiameresti la funzione e fornirai un valore per a e c, ma lascerai b come predefinito?

 foo( 10, ??, 5 ); 

A differenza di altri linguaggi (ad es. Python), gli argomenti di funzione in C / C ++ non possono essere qualificati per nome, come il seguente:

 foo( a = 10, c = 5 ); 

Se fosse ansible, allora gli argomenti predefiniti potrebbero essere ovunque nella lista.

Immagina di avere una funzione con questo prototipo:

 void testFunction(bool a = false, bool b = true, bool c); 

Supponiamo che abbia chiamato la funzione in questo modo:

 testFunction(true, false); 

Come si suppone che il compilatore capisca quali parametri ho inteso per fornire i valori?

Come sottolineato dalla maggior parte delle risposte, avere parametri predefiniti potenzialmente ovunque nell’elenco dei parametri aumenta la complessità e l’ambiguità delle chiamate di funzione (sia per il compilatore che, probabilmente, più importante per gli utenti della funzione).

Una cosa carina del C ++ è che spesso c’è un modo per fare ciò che vuoi (anche se non è sempre una buona idea). Se vuoi avere argomenti predefiniti per varie posizioni dei parametri, puoi quasi certamente farlo scrivendo sovraccarichi che semplicemente si girano e chiamano la funzione completamente parametrizzata in linea:

  int foo( int x, int y); int foo( int y) { return foo( 0, y); } 

E lì hai l’equivalente di:

  int foo( int x = 0, int y); 

Come regola generale, i parametri della funzione vengono elaborati dal compilatore e posizionati nello stack nell’ordine da destra a sinistra. Pertanto ha senso che tutti i parametri con valori predefiniti dovrebbero essere valutati per primi.

(Questo si applica a __cdecl, che tende ad essere l’impostazione predefinita per le dichiarazioni di funzione VC ++ e __stdcall).

È perché usa la posizione relativa degli argomenti per trovare a quali parametri corrispondono.

Potrebbe aver usato i tipi per identificare che non è stato fornito un parametro opzionale. Ma la conversione implicita potrebbe interferire con esso. Un altro problema potrebbe essere rappresentato dagli errori di programmazione che potrebbero essere interpretati come argomenti facoltativi che si eliminano invece di errori di argomento mancanti.

Per consentire a qualsiasi argomento di diventare facoltativo, dovrebbe esserci un modo per identificare gli argomenti per assicurarsi che non vi siano errori di programmazione o rimuovere ambiguità. Questo è ansible in alcune lingue, ma non in C ++.

Un’altra cosa che il comitato degli standard doveva prendere in considerazione era il modo in cui i parametri predefiniti interagiscono con altre funzionalità, come funzioni sovraccariche, risoluzione dei modelli e ricerca dei nomi. Queste caratteristiche interagiscono già in modi molto complessi e difficili da descrivere. Rendere i parametri di default in grado di apparire ovunque aumenterebbe la complessità.

Si tratta della convenzione di chiamata. Call Convention: quando si chiama una funzione, i parametri vengono messi in pila da destra a sinistra. per esempio

 fun(int a, int b, int c); 

lo stack è come questo: a b c so, se imposti il ​​valore predefinito da sinistra a destra in questo modo:

 fun(int a = 1, int b = 2, int c); 

e chiama così:

 fun(4,5); 

la tua chiamata significa impostare a = 4, b = 5 e c = nessun valore; // che è sbagliato!

se dichiari la funzione in questo modo:

fun(int a, int b = 2, int c = 3);

e chiama così: fun(4, 5);

la tua chiamata significa impostare a = 4, b = 5 e c = valore predefinito (3); // quale è giusto!

In conclusione, dovresti inserire il valore predefinito da destra a sinistra.

Jing Zeng è corretto. Vorrei aggiungere le mie osservazioni qui. Quando viene chiamata una funzione, gli argomenti vengono inseriti nello stack da destra a sinistra. Ad esempio, supponiamo tu abbia questa funzione arbitraria.

 int add(int a, int b) { int c; c = a + b; return c; } 

Ecco la cornice dello stack per la funzione:

 ------ b ------ a ------ ret ------ c ------ 

Questo diagramma sopra è lo stack frame per questa funzione! Come puoi vedere, la prima b viene spinta in pila, quindi una viene spinta in pila. Successivamente, l’indirizzo di ritorno della funzione viene inserito nello stack. L’indirizzo di ritorno della funzione mantiene la posizione in main () da dove la funzione è stata originariamente chiamata, e dopo che la funzione è stata eseguita, l’esecuzione del programma passa all’indirizzo di ritorno di quella funzione. Quindi qualsiasi variabile locale come c viene spinta in pila.

Ora la cosa fondamentale è che gli argomenti vengono inseriti nello stack da destra a sinistra. Fondamentalmente, tutti i parametri predefiniti forniti sono valori letterali, che sono memorizzati nella sezione di codice di un eseguibile. Quando l’esecuzione del programma incontra un parametro predefinito senza un argomento corrispondente, spinge quel valore letterale in cima allo stack. Quindi guarda a e spinge il valore dell’argomento in cima allo stack. Il puntatore dello stack punta sempre in cima allo stack, la variabile più recente. Quindi tutti i valori letterali che hai inserito nello stack come parametri predefiniti sono “dietro” al puntatore dello stack.

Probabilmente è stato più efficiente per il compilatore inserire rapidamente i valori letterali predefiniti arbitrari sullo stack, poiché non sono memorizzati in una posizione di memoria e creano rapidamente lo stack. Pensa a cosa sarebbe stato se le variabili fossero state prima inserite nello stack e poi quelle letterali. L’accesso a una posizione di memoria per la CPU richiede un tempo relativamente lungo rispetto alla rimozione di un valore letterale da un circuito o da un registro CPU. Poiché ci vuole più tempo per spingere le variabili nello stack rispetto ai letterali, i letterali dovrebbero aspettare, quindi l’indirizzo di ritorno dovrebbe aspettare e le variabili locali dovrebbero anche aspettare. Probabilmente non è una grande preoccupazione in termini di efficienza, ma questa è solo la mia teoria sul perché gli argomenti predefiniti sono sempre nelle posizioni più a destra dell’intestazione di una funzione in C ++. Significa che il compilatore è stato progettato come tale.