2013-05-24 8 views
8

Sto usando la libreria delle espressioni regolari di boost e sto scoprendo che determinare se una corrispondenza con nome è stata trovata e quindi usare quell'informazione è un po 'fastidiosa. Per rilevare una corrispondenza con nome, vorrei fare questo:Come posso creare una cascata pulita se la struttura in C++?

typedef boost::match_result<string::const_iterator> matches_t; 
typedef matches_t::const_reference match_t; 
boost::regex re("(?:(?<type1>aaaa)|(?<type2>bbbb)" /*...*/ "|(?<typeN>abcdefg)"); 
string str(SOME_STRING); 
matches_t what; 
boost::match_flag_type flags = boost::match_default; 

if(regex_search(str.cbegin(), str.cend(), what, re, flags)) 
{ 
    if((match_t type1 = what["type1"]).matched) 
    { 
    // do stuff with type1 
    } 
    else if((match_t type2 = what["type2"]).matched) 
    { 
    // do stuff with type2 
    } 
    // ... 
    else if((match_t typeN = what["typeN"]).matched) 
    { 
    // do stuff with typeN 
    } 
} 

Se ciò funzionasse, sarebbe fantastico. Scoping sarebbe limitato al corpo di if, la memoria può essere utilizzata in modo efficiente e sembra abbastanza pulita. Purtroppo, non funziona in quanto non è possibile definire una variabile all'interno di un elenco. :(

Questo avrebbe potuto essere una possibilità:

if(regex_search(str.cbegin(), str.cend(), what, re, flags)) 
{ 
    match_t found = what["type1"]; 
    if(found.matched) 
    { 
    // do stuff with type1 
    } 
    else if((found = what["type2"]).matched) 
    { 
    // do stuff with type2 
    } 
    // ... 
    else if((found = what["typeN"]).matched) 
    { 
    // do stuff with typeN 
    } 
} 

Ma match_t è un riferimento const quindi non è assegnabile (tl; dr Anche io non so che cosa il tipo di fondo è e generalmente io. non voglio davvero sapere come preferirei una soluzione più generica che potrei usare al di fuori di questo esempio di regex, anche std :: move() è stato usato attorno a ciò che [...] diventa ancora più dettagliato e la documentazione non dice che usa semantica mossa per sub_match.Tutto questo è ovvio, naturalmente, a causa del motivo indicato nella prima frase di questo paragrafo)

Un'altra opzione è quella di fare è questo:

if(regex_search(str.cbegin(), str.cend(), what, re, flags)) 
{ 
    match_t type1 = what["type1"]; 
    if(type1.matched) 
    { 
    // do stuff with type1 
    } 
    else { 
    match_t type2 = what["type2"]; 
    if(type2.matched) 
    { 
     // do stuff with type2 
    } 
    // ... 
      else { 
      match_t typeN = what["typeN"]; 
      if((match_t typeN = what["typeN"]).matched) 
      { 
       // do stuff with typeN 
      } 
      } 
    // ... 
    } 
    } 
} 

Il che non mi piace a causa di profonda nidificazione di parentesi graffe.

Forse aver abusato di una struttura ad anello con break s alla fine di ogni if corpo come questo:

if(regex_search(str.cbegin(), str.cend(), what, re, flags)) 
{ 
    do{ 
    { 
     match_t type1 = what["type1"]; 
     if(type1.matched) 
     { 
     // do stuff with type1 
     break; 
     } 
    } 
    { 
     match_t type2 = what["type2"]; 
     if(type2.matched) 
     { 
     // do stuff with type2 
     break; 
     } 
    } 
    // ... 
    { 
     match_t typeN = what["typeN"]; 
     if(typeN.matched) 
     { 
     // do stuff with typeN 
     break; 
     } 
    } 
    } while(0); 
} 

Che è meglio, ma ancora non ottimo. Utilizzando le macro, gran parte del rumore potrebbe essere nascosto alla vista. Come:

#define IF(declare, cond) do{{declare;if(cond){     
#define ELSE_IF(declare, cond) break;}}{declare; if(cond){  
#define ELSE break;}}{{           
#define END_IF break;}}}while(0);        

if(regex_search(str.cbegin(), str.cend(), what, re, flags)) 
{ 
    IF(match_t type1 = what["type1"], type1.matched) 
    { 
    // do stuff with type1 
    } 
    ELSE_IF(match_t type2 = what["type2"], type2.matched) 
    { 
    // do stuff with type2 
    } 
    // ... 
    ELSE_IF(match_t typeN = what["typeN"], typeN.matched) 
    { 
    // do stuff with typeN 
    } 
    END_IF 
} 

Le parentesi sono in realtà implicita nel macro, ma è una lettura più chiara da loro ribadendo.

Un'altra opzione a cui posso pensare è entrare nella classe boost :: sub_match e aggiungere una funzione di conversione per convertire quel tipo in un valore bool che restituirà il valore del membro matched. Quindi potrei dichiarare una variabile match_t nella espressione if e verrebbe automaticamente convertita in un valore booleano dal if. Non sono sicuro se sono ancora lì e non è generico.

Stilisticamente, sono quelli che propongo buoni o cattivi (solo gli ultimi 3 funzionano davvero, quindi probabilmente limiterei i commenti a loro).

Inoltre, qualcuno ha suggerimenti migliori? Per favore, spiega perché pensi che siano migliori.

+0

Si noti che 'if (foo = bar)' non è lo stesso di 'if (foo == bar)' in C++, il codice probabilmente non fa ciò che si desidera. Inoltre raccomando fortemente di usare le macro per "rimuovere la posta indesiderata" come nel tuo ultimo esempio. Non rimuove la "spazzatura" ma rende il codice incomprensibile. – Damon

+0

@Damon: Sì, lo so che = non è uguale a ==. Il codice fa esattamente ciò che voglio, per limitare l'ambito, ridurre il rumore e riutilizzare lo spazio dello stack (se il compilatore lo ottimizza). Sono un po 'd'accordo con te: i macro, ma riduce il rumore in eccesso a patto che tu sappia a cosa servono le macro, il che può rendere le cose più chiare. Ma YMMV. Quindi suppongo che tu preferisca il secondo con 'do {} while (0);' sans i macro? – Adrian

+0

Non sono sicuro, probabilmente scriverei la primissima versione (dichiarando le variabili nell'output 'if' esterno, quindi compila), dal momento che è la versione meno offuscata, secondo me. Ma alla fine della giornata, si tratta di gusti personali. E metterei doppie parentesi attorno ai compiti all'interno di "if" per rendere esplicito che non sono accidenziali (con gli avvisi corretti abilitati, questo è anche ciò che, ad esempio, GCC suggerisce di fare). – Damon

risposta

5

In generale si raccomanda di evitare annidati if s - fanno il codice più difficile da leggere. Se è nidificato se, dovrebbe probabilmente essere sostituito da una chiamata di funzione.

Nel tuo caso, è necessario utilizzare un ciclo.

tuo secondo esempio:

if(regex_search(str.cbegin(), str.cend(), what, re, flags)) 
{ 
    match_t found = what["type1"]; 
    if(found.matched) 
    { 
    // do stuff with type1 
    } 
    else if((found = what["type2"]).matched) 
    { 
    // do stuff with type2 
    } 
    // ... 
    else if((found = what["typeN"]).matched) 
    { 
    // do stuff with typeN 
    } 
} 

implora per un ciclo:

const char *types[] = {"type1", "type2", "typeN", 0}; 
for(const char **cur = types; *cur; cur++){ 
    found = what[*cur]; 
    if (found.matched){ 
     //initiate robot uprising 
     break; 
    } 
} 

Tutti gli altri esempi (IMO) sono uno stile di codifica male. Preferisco mantenere loops e ifs short. Se non si adatta a 20 linee di codice, allora è meglio fare qualcosa di molto complicato (che non è il tuo caso). Se non fa nulla di complicato, deve essere ristrutturato.

+0

Mi piace decisamente perché è facilmente scalabile, è pulito e facile da usare con una tabella di salto del puntatore di funzione. Penso che abbiamo un vincitore! :) Sebbene IMO avrebbe dovuto essere usato uno stile iteratore standard invece di usare un NULL alla fine dell'array. – Adrian

+0

Oh, e la tua sintassi è lontana. – Adrian

+0

Adrian: "la sintassi è lontana"? Intendi cosa "{alla fine della linea" o qualcos'altro? – SigTerm

3

Si potrebbe fare qualcosa di simile (notare che questo codice non è testato contro un compilatore)

// create a map between match types and actions 
std::map<std::string, std::function<match_t>> actions; 
actions["type1"] = [&](match_t type) {...}; 

// iterate through the actions map and check each match type 
for(auto kvp : actions) 
{ 
    match_t type = what[kvp.first]; 
    if(type.matched) 
    { 
     // do stuff with this result 
     kvp.second(type); 
    } 
} 
+0

Hmmm, interessante. Hai dimenticato la pausa alla fine del tuo corpo se. Altri potenziali problemi sono 1. non è possibile definire quale deve essere prima testato (sebbene ciò possa essere fatto usando un vettore di una tupla). E 2. sposta il codice dal punto di esecuzione. Ma usare una lambda come questa è decisamente interessante. Potrebbe anche essere fatto con una funzione a tutti gli effetti. +1 per l'ingegno. :) – Adrian

+0

Quali sono le opinioni delle persone sulla leggibilità di questo? Un'azione dovrebbe essere piuttosto breve. Non più di 10 righe. – Adrian

+1

È possibile sostituire la mappa con un elenco o un altro contenitore di sequenza in modo da poter controllare l'ordine della valutazione delle partite. –

0

È possibile scrivere un involucro di match_t con un sovraccarico di operator bool:

struct bmatch 
{ 
    matches_t::const_reference ref; 
    bmatch(matches_t::const_reference r) 
    :ref(r) 
    {} 

    operator bool() const 
    { 
     return ref.matched; 
    } 
}; 

E poi :

if (bmatch type1 = what["type1"]) 
{ //Use type2.ref 
} 
else if (bmatch type2 = what["type2"]) 
{ //Use type2.ref 
} 

Per punti aggiuntivi è possibile anche sovraccaricare operator->:

matches_t::const_reference operator->() const 
    { 
     return ref; 
    } 
+0

Ah sì, molto meglio di modificare il codice in una libreria. :) – Adrian

+0

IMO, dichiarazioni come questa 'if (bmatch type1 = what [" type1 "])' non sono molto leggibili e preferisco evitarli. –

Problemi correlati