2013-08-21 32 views
12

Durante la lettura dei codici, troveremo alcune funzioni come questa.Come possiamo chiamare una funzione con "parametro = valore" in C++?

g_spawn_async(NULL, new_argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL); 

Penso che nessuno possa capire qual è il significato di ogni parametro. Per capire il codice, dobbiamo trovare la dichiarazione della funzione.

gboolean g_spawn_async    (const gchar *working_directory, 
             gchar **argv, 
             gchar **envp, 
             GSpawnFlags flags, 
             GSpawnChildSetupFunc child_setup, 
             gpointer user_data, 
             GPid *child_pid, 
             GError **error); 

Come possiamo chiamare una funzione come il seguente formato in C++?

g_spawn_async(working_directory=NULL, 
       argv=new_argv, 
       envp=NULL, 
       flags=G_SPAWN_SEARCH_PATH, 
       child_setup=NULL, 
       user_data=NULL, 
       child_pid=NULL, 
       error=NULL); 

Penso che questo sarà più leggibile e posso capire il codice senza cercare la dichiarazione della funzione.

So che Python può farlo. Come può C++ fare questo?

+2

@SanthoshPai Non sono d'accordo con te. La seconda chiamata viene superata solo se le variabili vengono dichiarate prima della chiamata alla funzione e questo non è ciò che l'OP sta chiedendo – Amadeus

+0

Se si creano globali "non utilizzati" con tali nomi, [Il codice viene compilato così com'è] (http: //coliru.stacked -crooked.com/view?id=a1124762016375ec2b82bea6eed4622b-c96156d6cc95286981b0e9deef2eefae). Non penso che sia una buona idea, specialmente per il C++. –

risposta

8

C++ non supporta questo in modo nativo, quindi non è possibile farlo con qualsiasi funzione vecchia esistente. Se stai creando la tua API, puoi usare quello che viene chiamato lo Named Parameter Idiom per emularlo. L'esempio dal collegamento:

File f = OpenFile("foo.txt") 
      .readonly() 
      .createIfNotExist() 
      .appendWhenWriting() 
      .blockSize(1024) 
      .unbuffered() 
      .exclusiveAccess(); 
+1

Puoi anche racchiudere l'API esistente con qualcosa di simile a questo. – fscan

+1

Sembra migliore, ma ciò comporterà più metodi pubblici che distruggono l'incapsulamento. – Yuanhang

+2

@Yuanhang: No, non risulta in più metodi pubblici. Guardate ancora, i metodi sono sull'oggetto 'OpenFile', dove sta creando un oggetto' File'. L'oggetto 'File' non ha metodi pubblici aggiuntivi, a parte un nuovo costruttore di conversioni. –

5

Questo non è possibile in C o C++.

Capisco i tuoi dolori con questo. Personalmente ritengo che sia un segno di cattiva progettazione avere una funzione che richiede oltre 9000 argomenti, soprattutto se la maggior parte di essi sono valori NULL o segnaposto. Molte funzioni standardizzate POSIX, ad esempio, prendono una sorta di struct che accumula tutti i valori necessari in un argomento semplice e comprensibile.

+1

È possibile in C++, ma il meccanismo sottostante potrebbe non essere carino. Si potrebbe dichiarare la funzione da chiamare con tutti i parametri come classe Foo. La classe Foo conterrebbe un'unione di tutti i possibili tipi di parametri e un enum per i parametri. Quindi 'working_directory',' child_setup', et cetera potrebbe essere oggetti globali di classi con definizioni di 'operator =', in modo che 'working_directory = NULL' sia valutato a qualcosa che ha registrato il NULL nel sindacato e impostato l'enum al valore per il parametro 'working_directory'. Quindi la funzione chiamata deve decodificare i parametri passati ad essa. –

+1

@EricPostpischil: Anche se funzionerà sicuramente, è ancora più brutto e soggetto a errori rispetto a una tonnellata di parametri su una funzione. – arne

+1

È perfettamente possibile in C++, e nemmeno particolarmente difficile, concettualmente. (Ci vuole un sacco di codice in più.D'altra parte, sarei certamente d'accordo con te sul fatto che il cattivo design abbia una funzione che richiede più di 9000 parametri, ma una sorta di aiuto potrebbe essere utile anche per funzioni che richiedono molto meno. Non ho mai visto una funzione con 9000 parametri, ma ho avuto a che fare con alcuni in codice legacy con 40 o 50 parametri. –

4

No, non è possibile. Ma puoi assegnare i valori NULL alle variabili e poi passarli come parametri se questo ti aiuta con la tua leggibilità!

+0

Ma questo dovrà definire variabili extra e sarà meno efficiente senza l'ottimizzazione del compilatore. – Yuanhang

+6

@Yuanhang: Fortunatamente, i compilatori _do_ hanno ottimizzazione! Evviva! –

+0

Cura di spiegare il downvote? – Paddyd

1

È certamente possibile. Non è nemmeno particolarmente difficile, ma coinvolge molto codice. Qualcosa di simile a quanto segue potrebbe essere utilizzato:

enum MyFuncParamId 
{ 
    myA, 
    myB, 
    myC, 
    myD, 
    unknown 
}; 

class MyFuncParam 
{ 
    union OneParam 
    { 
     double aOrB; 
     C c; 
     int d; 

     OneParam() {} 
     ~OneParam() {} 
    }; 
    OneParam myParam; 
    MyFuncParamId myId; 

public: 
    MyFuncParam(MyFuncParamId id, double value) 
     : myId(id) 
    { 
     switch (myId) { 
     case myA: 
     case myB: 
      myParam.aOrB = value; 
      break; 

     case myC: 
      assert(0); 
      abort(); 

     case myD: 
      myParam.d = value; 
      break; 
     } 
    } 
    MyFuncParam(MyFuncParamId id, C const& value) 
     : myId(id) 
    { 
     switch (myId) { 
     case myA: 
     case myB: 
     case myD: 
      assert(0); 
      abort(); 

     case myC: 
      new (&myParam.c) C(value); 
      break; 
     } 
    } 
    MyFuncParam(MyFuncParamId id, int value) 
     : myId(id) 
    { 
     switch (myId) { 
     case myA: 
     case myB: 
      myParam.aOrB = value; 
      break; 

     case myC: 
      assert(0); 
      abort(); 

     case myD: 
      myParam.d = value; 
      break; 
     } 
    } 
    MyFuncParam(MyFuncParam const& other) 
     : myId(other.myId) 
    { 
     switch (myId) { 
     case myA: 
     case myB: 
      myParam.aOrB = other.myParam.aOrB; 
      break; 
     case myC: 
      new (&myParam.c) C(other.myParam.c); 
      break; 
     case myD: 
      myParam.d = other.myParam.d; 
      break; 
     } 
    } 
    ~MyFuncParam() 
    { 
     switch(myId) { 
     case myC: 
      myParam.c.~C(); 
      break; 
     } 
    } 
    MyFuncParam& operator=(MyFuncParam const&) = delete; 
    friend class MyFuncParamGroup; 
}; 

class MyFuncRouter 
{ 
    MyFuncParamId myId; 
public: 
    MyFuncRouter(MyFuncParamId id) : myId(id) {} 

    MyFuncParam operator=(double value) 
    { 
     return MyFuncParam(myId, value); 
    } 

    MyFuncParam operator=(C const& value) 
    { 
     return MyFuncParam(myId, value); 
    } 

    MyFuncParam operator=(int value) 
    { 
     return MyFuncParam(myId, value); 
    } 
}; 

static MyFuncRouter a(myA); 
static MyFuncRouter b(myB); 
static MyFuncRouter c(myC); 
static MyFuncRouter d(myD); 

struct MyFuncParamGroup 
{ 
    bool aSet; 
    bool bSet; 
    bool cSet; 
    bool dSet; 
    double a; 
    double b; 
    C c; 
    int d; 
    MyFuncParamGroup() 
     : aSet(false) 
     , bSet(false) 
     , cSet(false) 
     , dSet(false) 
    { 
    } 
    void set(MyFuncParam const& param) 
    { 
     switch (param.myId) { 
     case myA: 
      assert(!aSet); 
      aSet = true; 
      a = param.myParam.aOrB; 
      break; 
     case myB: 
      assert(!bSet); 
      bSet = true; 
      b = param.myParam.aOrB; 
      break; 
     case myC: 
      assert(!cSet); 
      cSet = true; 
      c = param.myParam.c; 
      break; 
     case myD: 
      assert(!dSet); 
      dSet = true; 
      d = param.myParam.d; 
      break; 
     } 
    } 
}; 

void 
myFunc(
    MyFuncParam const& p1, 
    MyFuncParam const& p2, 
    MyFuncParam const& p3, 
    MyFuncParam const& p4) 
{ 
    MyFuncParamGroup params; 
    params.set(p1); 
    params.set(p2); 
    params.set(p3); 
    params.set(p4); 
    std::cout << "a = " << params.a 
     << ", b = " << params.b 
     << ", c = " << params.c 
     << ", d = " << params.d 
     << std::endl; 
} 

Note:

  1. Ho usato C++ 11 qui. La stessa cosa può essere fatto in precedenti versioni di C++, sostituendo il tipo C nella union con unsigned char c[sizeof(C)];, aggiungendo qualcosa al union per garantire il corretto allineamento (se necessario), e un sacco di tipo casting.

  2. questo sarebbe molto più semplice con boost::variant (invece di del union) e boost::optional (in MyFuncParamGroup). Non avevo Boost disponibile, quindi ho fatto la maggior parte di quello che fanno esplicitamente. (Che naturalmente, rende il codice molto più a lungo.)

  3. Sono intenzionalmente utilizzati due parametri con lo stesso tipo, e un tipo definito dall'utente (C) con i costruttori non banali, per mostrare come queste sono gestiti

  4. Probabilmente vorreste un po 'più incapsulamento e un po' più di controllo degli errori .

Ma la vera domanda è: vuoi davvero percorrere questa strada? La quantità di codice aumenta in modo lineare con il numero di parametri . E con qualsiasi editor decente, puoi temporaneamente inserire nella lista dei parametri dalla dichiarazione della funzione a destra dello schermo e compilare i parametri a sinistra, direttamente sul lato della dichiarazione dei parametri. (In gvim, io di solito modalità di modifica uso di blocco per questo, ma :vsplit può anche essere usato .)

1

I parametri denominati sono molto utili e stavo anche considerando che in una lingua dovrebbero essere l'unico modo per chiamare una funzione tranne un singolo parametro ovvio se quello è presente.

sin(x)    // the obvious main parameter 
sin(x, unit=DEGREE) // any other must be named 

C++ purtroppo non li ha.

Ancora più importante C++ manca della capacità di metaprogrammazione necessaria per essere in grado di implementarli.

Mentre ci sono trucchi che possono provare a simulare in qualche modo i parametri denominati e anche se il codice risultante può sembrare quasi ragionevole, ciò che è totalmente indecente è il codice che devi scrivere per ottenere quella simulazione e che non c'è modo in C++ per generare quel codice.

Quello che voglio dire è che mentre è possibile scrivere una funzione accettando i parametri denominati emulati ragionevolmente, il codice per la funzione stessa è orribile e non può essere generato automaticamente in C++. Ciò significa che nessuno scriverà le funzioni in questo modo, quindi la funzione non è ancora presente.

La mia ipotesi è che i parametri denominati mancano a causa di un mix di ignoranza (esistevano molto prima che C++ fosse inventato, probabilmente anche prima di C) e di orgoglio (anche in C++ 11 le abilità di metaprogrammazione del linguaggio sono patetiche e cose banale come dire enumerare in fase di compilazione i membri di una struttura oi parametri di una funzione è impossibile).

Problemi correlati