Sintassi preferita per chiamare una funzione con parametri restituiti da un'altra funzione

6

Ho circa una settimana di studio del C ++ quindi ti prego di sopportare me.

Quindi diciamo che ho una funzione che restituisce un valore che ho bisogno di usare in un'altra funzione.

È meglio fare in modo che uno assegni per primo il valore restituito a un'altra variabile o è meglio chiamare semplicemente la funzione come parametro dell'altro? Entrambe dovrebbero essere sintassi valida (credo).

Esempio semplificato:

//method 1
int x = foo(boo(1));

//method 2
int a = boo(1);

int x = foo(a);

Sto iniziando a dover utilizzare funzioni che coinvolgono molti parametri, e posso vedere come il metodo 1 potrebbe potenzialmente portare a un codice più confuso.

//ResolveProcess is a function that resolves dwProcessId using an input

//method 1
HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ResolveProcess(processName));

//method 2
DWORD processID = ResolveProcess(processName);
HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);

Il link non ha informazioni su quale sia il migliore (di solito lo fa), quindi ho pensato di chiedere qui.

    
posta EndingLegacy 16.09.2016 - 04:20
fonte

5 risposte

11

Nella maggior parte dei casi, si tratta della versione più leggibile, che è spesso negli occhi di chi guarda. In casi semplici, la composizione è molto chiara, ma oltre un certo punto, la verbosità aggiunge chiarezza. In effetti, molte persone che trovano i parametri booleani meno leggibili daranno loro un nome, quindi possono essere capiti senza andare alla documentazione, come:

DWORD processID = ResolveProcess(processName);
BOOL inheritHandle = FALSE;
HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, inheritHandle, processID);

È anche molto importante tenere a mente con C ++ se hai outer(inner()) e inner() restituisce un nuovo puntatore, quindi outer() deve essere responsabile della sua liberazione, o causerai una perdita di memoria. Questo non è uno schema tipico a causa del modo in cui le dipendenze si accoppiano.

Molto spesso la funzione che chiama outer(inner()) è responsabile della liberazione della memoria, e quindi dovrebbe mantenere un riferimento al puntatore restituito da inner() . Questo è il motivo per cui lo stile di composizione è visto molto meno frequentemente in C ++ rispetto ai linguaggi raccolti con garbage.

    
risposta data 16.09.2016 - 06:13
fonte
4

Quale è meglio dipende interamente dalla situazione.

Questa:

int x = foo(boo(1));

si chiama composizione funzionale. Funziona. Va bene. A meno che tu non abbia bisogno di ciò che boo() ha restituito per qualcosa di diverso dal semplice passaggio a foo() .

Questa:

int a = boo(1);

int x = foo(a);

è una possibilità mancata di rendere il codice più leggibile dando a a un nome più descrittivo di a . Certo, ho ancora accesso a a ma se non ne ho bisogno e questo nome non aiuta, è uno spreco di prezioso spazio sullo schermo.

Una situazione correlata:

a().b();

vs

C c = a();

c.b();

Ancora una volta è difficile vedere che i due liner sono meglio, soprattutto perché ha questo nome inutile.

Se non puoi essere preso la briga di dargli un buon nome e ne hai solo bisogno per una cosa, personalmente, sto bene con le versioni più brevi.

Se desideri saperne di più su questo le versioni più brevi si avvantaggiano di qualcosa chiamato oggetto temporaneo senza nome .

Se davvero non riesci a pensare ad un buon nome, ma non ti piace quanto sia denso quello che è un liner, puoi fluffare il codice in questo modo:

int x = foo(
    boo(1)
);

Questo stile significa anche che se si genera un'eccezione, saprai da dove proviene solo dal numero di riga.

    
risposta data 16.09.2016 - 04:34
fonte
1

A parte i problemi di proprietà e di leggibilità menzionati nelle altre risposte, dovremmo pensare alla debuggibilità. Quando una affermazione diventa grande e complessa, mi pongo due domande:

  • Mi mancherebbe qualcosa di importante quando passo a questa istruzione completa in un debugger? In tal caso, dividere l'istruzione in modo che ogni istruzione contenga solo un'operazione non banale.

  • Vorrei esaminare un risultato intermedio in un debugger? In tal caso, introdurre una variabile.

Nella mia esperienza, progettare per la debugabilità è molto più importante in C ++ che in altri linguaggi, poiché il compilatore distrugge molte informazioni utili che sono ancora accessibili rispetto ad altre lingue. In particolare, le eccezioni in Java o Python hanno tracce di stack e puntano a una linea specifica; le eccezioni in C ++ no. Una volta che puoi riprodurre un bug, eseguire il programma sotto un debugger è di solito il modo più veloce per localizzare il problema.

Ho scoperto che le chiamate alla funzione di composizione / nidificazione portano molto raramente a un codice migliore e più semplice in C ++. Indipendentemente dal linguaggio, l'introduzione di variabili ben definite per i risultati intermedi tende a rendere il codice più semplice e più autodocumentante.

    
risposta data 16.09.2016 - 08:13
fonte
1

Se avessimo bisogno di un motivo in più per separare le chiamate, aggiungendo una variabile extra, è sicuramente il controllo degli errori. Il tuo esempio di codice utilizza l'API Win32; e questa API non utilizza eccezioni e / o RAII. Quindi i valori di ritorno tutti devono essere controllati.

Una funzione come ResolveProcess() può fallire e restituirà un valore esotico (ad es. 0, UINT32_MAX, ...). Lo stesso vale per OpenProcess() (è necessario verificare che l'handle restituito sia valido), quindi questo non dovrebbe mai essere passato direttamente alla funzione successiva. Inoltre, devi CloseHandle del risultato se non è valido, che è qualcosa che il costrutto Frobnicate(OpenProcess(...)) non ti consente di fare.

Anche se la tua domanda non riguarda l'API Win32, nella mia esperienza, molte volte sei tentato di comporre chiamate come questa, hai bisogno di separarle alla successiva iterazione. O perché vuoi controllare i valori di ritorno, o vuoi registrare / stampare / ... alcune informazioni, o perché rende il codice più chiaro, ecc.

La buona notizia è che, se necessario, tornerai comunque. Segui il tuo istinto.

E non dimenticare di controllare i codici di errore.

    
risposta data 16.09.2016 - 15:00
fonte
0

Sfortunatamente la risposta è molto: dipende .

A volte, è più chiaro e meglio assegnare a una variabile, a volte è meglio non farlo, e non esiste una singola regola che ti dirà cosa fare. Comunque suggerirei tre regole per guidare l'utente:

  1. Se la chiamata alla funzione è un getter, non è necessaria una variabile intermedia.
  2. Se la chiamata alla funzione ha un effetto banalmente ovvio, non hai bisogno di una variabile intermedia.
  3. Se la chiamata alla funzione ha effetti collaterali significativi, allora do necessita di una variabile intermedia.

Oltre a ciò è una questione di gusti, ma considerate le differenze in ciò che viene espresso:

int foodValue = foo(b);
int doodify = doo(d);
float count = bar(foodValue, doodify);

contro

float count = bar(foo(b), doo(d));

Nel primo caso, esprimi in modo più chiaro cosa restituiscono foo(b) e doo(d) (questo punto è più chiaro nel codice reale in cui hai le variabili ben denominate, ovviamente!) e attiri più attenzione al richiamo della funzione in questi passaggi. In quest'ultimo si sottolinea che il passo principale è chiamare bar e suggerire al lettore che foo e doo stanno facendo cose meno importanti. Incoraggi il lettore a considerare il tutto come un'unità in modo che possa leggere velocemente e passare alla prossima cosa.

Troppo del primo porta a un codice tentacolare che fa ben poco con ogni porzione di spazio sullo schermo; troppo tardi porta a un codice denso e impenetrabile che è difficile da digerire e comprendere. Solo l'esperienza può insegnarti come bilanciare questi fattori concorrenti.

    
risposta data 04.12.2018 - 10:39
fonte

Leggi altre domande sui tag