2012-05-17 20 views
13

Quindi ho questo enorme albero che è fondamentalmente un grosso interruttore/caso con chiavi stringa e chiamate di funzioni diverse su un oggetto comune a seconda della chiave e di un pezzo di metadati.Metaprogramming C/C++ usando il preprocessore

Ogni voce appare sostanzialmente in questo modo

} else if (strcmp(key, "key_string") == 0) { 
    ((class_name*)object)->do_something(); 
} else if (... 

dove do_something può avere diverse invocazioni, quindi non posso semplicemente usare puntatori a funzione. Inoltre, alcune chiavi richiedono che l'oggetto venga trasmesso a una sottoclasse.

Ora, se dovessi codificare questo in un linguaggio di livello superiore, userei un dizionario di lambda per semplificare questo.

mi venne in mente che avrei potuto usare le macro per semplificare questo a qualcosa come

case_call("key_string", class_name, do_something()); 
case_call(/* ... */) 

dove case_call sarebbe una macro che espandere questo codice per il primo frammento di codice.

Tuttavia, sono molto sul recinto se questo sarebbe considerato di buon gusto. Voglio dire, ridurrebbe il lavoro di digitazione e migliorerebbe l'ESSENZIALITÀ del codice, ma in realtà sembra abusare del sistema macro in qualche modo.

Vuoi andare su quella strada, o meglio scrivere tutto? E quale sarebbe il tuo ragionamento per farlo?

Modifica

Alcuni chiarimenti:

Questo codice è utilizzato come strato collante tra un'API scripting semplificata che accede diversi aspetti di come semplici proprietà valori-chiave C++ API. Le proprietà sono implementate in diversi modi in C++: alcuni hanno metodi getter/setter, alcuni sono impostati in una struttura speciale. Le azioni di scripting fanno riferimento a oggetti C++ convertiti in una classe base comune. Tuttavia, alcune azioni sono disponibili solo su alcune sottoclassi e devono essere abbattute.

In fondo alla strada, posso cambiare l'attuale API C++, ma per il momento, deve essere considerato immutabile. Inoltre, questo deve funzionare su un compilatore incorporato, quindi boost o C++ 11 non sono (purtroppo) non disponibili.

+5

Perché i voti da chiudere - questa è una domanda valida e valida. Cosa mi manca qui? –

+3

È una bella domanda, ma "Questa domanda non è adatta al nostro formato di domande e risposte. Prevediamo che le risposte implichino generalmente fatti, referenze o competenze specifiche; * questa domanda richiederà probabilmente opinioni, dibattiti, discussioni, sondaggi o estensioni discussione. * " – Fanael

+0

È già al punteggio +5, quindi quel tipo dice che gli altri, me compreso, sono interessati al risultato. La risposta qui sotto è già abbastanza carina. –

risposta

6

Ti suggerirei di invertire leggermente i ruoli. Stai dicendo che l'oggetto è già una classe che sa come gestire una certa situazione, quindi aggiungi un virtual void handle(const char * key) nella tua classe base e consenti all'oggetto di verificare l'implementazione se si applica a esso e fa tutto ciò che è necessario.

Questo non solo eliminerebbe la lunga catena if-else-if, ma sarebbe anche più sicuro per il tipo e offrirà una maggiore flessibilità nella gestione di tali eventi.

+0

Questo non elimina la lunga catena if-else-if, semplicemente lo sposta/riorganizza. Tuttavia, questa è ancora una buona idea. –

+1

@MooingDuck Non eliminerebbe i singoli ifs, ma eliminerebbe il lungo elenco centrale di casi che devono essere gestiti. Ogni controllo sarebbe nella classe in cui avviene la gestione effettiva, quindi i luoghi che devono essere modificati per aggiungere nuove funzionalità sarebbero vicini. – Fozi

+0

Questa è un'idea interessante. Mi richiederebbe di modificare le classi di implementazione, che per una serie di motivi non sono desiderabili in questo momento, ma questa è certamente una buona idea. – bastibe

6

Questo mi sembra un uso appropriato delle macro. Dopotutto, sono fatti per eliminare la ripetizione sintattica. Tuttavia, quando si ha una ripetizione sintattica, non è sempre colpa della lingua, probabilmente ci sono scelte progettuali migliori là fuori che consentono di evitare completamente questa decisione.

La saggezza generale è quella di utilizzare un tasti di mappatura tabella per azioni:

std::map<std::string, void(Class::*)()> table; 

Poi cercare e richiamare l'azione in un colpo solo:

object->*table[key](); 

Oppure utilizzare find per verificare la presenza di guasti:

const auto i = table.find(key); 
if (i != table.end()) 
    object->*(i->second)(); 
else 
    throw std::runtime_error(...); 

Ma se come dici tu non c'è una firma comune per le funzioni (es., non è possibile utilizzare i puntatori funzione membro), quindi quello che effettivamente deve fare dipende dai dettagli del progetto, che non conosco. Potrebbe essere che una macro sia l'unico modo per elidere la ripetizione che stai vedendo, o potrebbe essere che ci sia un modo migliore per farlo.

Chiediti: perché le mie funzioni accettano argomenti diversi? Perché sto usando i cast? Se stai inviando il tipo di un oggetto, è probabile che tu debba introdurre un'interfaccia comune.

+0

"dove do_qualcosa può avere diverse chiamate, quindi non posso usare solo i puntatori di funzione." <- Sembra che non tutte le funzioni saranno 'void (Class :: *)()'. Alcuni potrebbero richiedere argomenti. Almeno questo è quello che ho capito. – mfontanini

+0

@fontanini: Ah, mi sono perso. Modificherà. –

+0

In realtà, ho due tipi distinti di invocazioni, quindi in realtà funzionerebbe se usassi due mappe. – bastibe