Combinando gli argomenti di una funzione usando una singola struttura

-1

Quali sono le conseguenze (pro e contro) dell'utilizzo di una singola struttura come argomento, anziché un elenco di parametri. Alcuni dei vantaggi che vedo sono:

  1. crea più parametri predefiniti.
  2. Non è necessario rispondere sull'ordine dei parametri.
  3. In caso di refactoring, non è necessario modificare la firma del file metodo.

Alcuni svantaggi che mi aspetto sono:

  • Potrebbe essere difficile gestire i parametri con "&".
  • Potrebbe non avere senso usare "overloading di funzioni" e potremmo dover mettere tutti i parametri possibili in una singola struttura e tutte le combinazioni dei parametri potrebbero non avere senso tutto il tempo!

Quali sono gli altri pro e contro di questo approccio?

    
posta Soo 05.07.2018 - 18:02
fonte

1 risposta

7

OK, quindi quello di cui stai parlando è una funzione del modulo:

struct func_params
{
  int param1;
  float param2;
  ...
};

void func(func_params params) {...}

Supporrò che l'ipotetico func_params sia legittimamente utile solo per chiamare func . Che i valori func_params memorizza non hanno alcuna connessione logica oltre al fatto che sono ciò che func deve fare il suo lavoro.

Pre-C ++ 11, gli utenti chiamerebbero generalmente la tua funzione usando un codice come questo:

func_params params = {...};
func(params);

Data l'ipotesi di cui sopra, viene creata una variabile stack la cui utilità non si estende oltre la chiamata a func .

Ma dal momento che hai taggato la tua domanda C ++ 11, gli utenti possono chiamare la tua funzione con func({...}) invece tramite l'inizializzazione della lista.

Tuttavia, in entrambi i casi, l'ordine conta . L'ordine dei valori in func_params viene eseguito direttamente sull'ordine dei valori nell'elenco inizializzatore {...} .

Ovviamente, le regole aggregate di C ++ 11 non consentono gli inizializzatori dei membri predefiniti negli aggregati. C ++ 14 fa.

Ora sì, un utente potrebbe fare questo:

func_params params;
params.param1 = ...;
params.param2 = ...;
...
func(params);

Ma seriamente, chi lo vuole scrivere? E chi lo vuole leggere? Pertanto, quando possibile, gli utenti eviteranno la verbosità aggiunta della forma lunga, a favore della forma breve. E con il modulo breve, i valori predefiniti nel mezzo di una sequenza non contano.

Gli inizializzatori designati di C ++ 20 ti salveranno dal problema del valore predefinito, ma non dalla dipendenza dall'ordine. Sì, ti permetteranno di fare func({.param1 = ..., .param2 = ..., ...}) , ma la funzione C ++ 20 non ti permette di riordinarli. È possibile omettere gli elementi (verranno inizializzati o inizializzati in base ai valori iniziali degli inizializzatori dei membri), ma non è consentito riordinare i membri aggregati specificati.

Quindi "Non c'è bisogno di rispondere sull'ordine dei parametri". non è corretto. O almeno, correggi solo la forma lunga che le persone eviteranno assiduamente.

Ma c'è il problema ABI. L'ABI di func è infine legato a func_params . Se questo è in un'interfaccia, le modifiche a func_params interesseranno tutti coloro che la utilizzano. Quindi, mentre non direttamente modifica la firma, lo stai facendo in modo efficace.

E questo sarebbe altrettanto vero se func ha preso func_params come const& piuttosto che come valore. Immagina cosa succederebbe se fornissi un "parametro" precedentemente non predefinito come valore predefinito. Ogni utente che ha chiamato la tua funzione dovrebbe essere ricompilato, poiché è compito del chiamante riempire i valori predefiniti.

Per me, fare questo è utile solo in C ++ 20 land dove si ha accesso agli inizializzatori designati. Perché? Perché ti permette di avere l'equivalente dei parametri nominati. Quindi se una funzione ha un gran numero di parametri diversi, puoi vedere i nomi di questi valori nel sito di chiamata. Questo rende più facile per le persone vedere quale sia il significato di ciascun parametro.

Oh, certo, puoi farlo in modo esteso, dove dichiari una variabile e imposta i valori che vuoi specificamente. Ma ancora, chi vuole scrivere o leggere quello?

E anche in questo caso, è utile solo per funzioni con un numero elevato di parametri. Di solito è meglio evitare tali scenari, per ridurre i conteggi dei parametri di funzione creando aggregazioni significative di essi.

Ricorda quell'ipotesi che ho fatto all'inizio? Più spesso, le funzioni che richiedono molti parametri avranno alcune connessioni significative tra determinati gruppi di parametri. Una funzione che verifica se un punto 2D è in un rettangolo potrebbe essere scritto come:

bool pt_in_rect(int rect_ul_x, int rect_ul_y, int rect_br_x, int rect_br_y, int test_x, int test_y);

Ma non è più ragionevole?

struct point2d { int x; int y; };
bool pt_in_rect(point2d rect_ul, point2d rect_br, point2d test);

E non è ancora meglio?

struct rect { point2d top_left; point2d bottom_right};
bool pt_in_rect(rect box, point2d test);

Questo è non equivalente a ciò di cui stai parlando. Perché? Poiché rect e point2d hanno un significato semantico. Non sono aggregazioni arbitrarie di valori; puoi immaginare dozzine o centinaia di funzioni che operano su rettangoli o punti 2D. Queste aggregazioni danno un significato ai loro valori dei componenti.

Non si costruiscono queste strutture solo perché esiste pt_in_rect ; li costruisci perché hanno senso e l'interfaccia di pt_in_rect diventa molto più digeribile a causa di ciò.

Ora, naturalmente, ci sono momenti in cui una funzione ha veramente bisogno di molti valori genuinamente disparati, senza un vero significato tra le collezioni. Ma questi casi sono abbastanza rari. Nella maggior parte dei casi, puoi ridurre i conteggi dei parametri fornendo delle astrazioni ragionevoli sui tipi di dati, come sopra.

    
risposta data 05.07.2018 - 19:14
fonte

Leggi altre domande sui tag