2010-06-29 17 views
14

Questo è il mio codice (semplificazione di un problema di vita reale):qual è una buona alternativa a questo brutto costrutto, in C++?

class Foo { 
public: 
    void f(const string& s) { 
    if (s == "lt") { 
     return lt(); 
    } else if (s == "lte") 
     return lte(); 
    } else if (s == "gt") 
     return gt(); 
    } else if (s == "gte") 
     return gte(); 
    } 
    } 
    void lt() { /* skipped */ } 
    void lte() { /* skipped */ } 
    void gt() { /* skipped */ } 
    void gte() { /* skipped */ } 
}; 

Ecco come lo farei in PHP/Python/JavaScript/molte altre lingue (ad esempio in PHP):

class Foo { 
    function f($s) { 
    return $this->$s(); 
    } 
    function lt() { /* skipped */ } 
    function lte() { /* skipped */ } 
    function gt() { /* skipped */ } 
    function gte() { /* skipped */ } 
} 

Come posso rendere il mio codice C++ elegante come questo esempio PHP? Grazie in anticipo.

+5

cosa succederebbe in PHP se l'argomento $ s è sbagliato? – Arseny

+3

un test può essere aggiunto, ma penso che la domanda non riguardi cosa si può aggiungere per evitare la situazione nel codice PHP – ShinTakezou

+0

Perché non usare un interruttore? –

risposta

37

Non c'è riflessione in C++. Tuttavia, qualcosa come un std::map<std::string, void (Foo::*)()> dovrebbe fare il trucco.


MODIFICA: Ecco un codice brutto per farlo in modo sostenibile. Tenere presente quanto segue:

  • Questo può probabilmente essere migliorata in modo diverso
  • Si prega di aggiungere il codice a che fare con i token inesistenti. Non ho effettuato alcun controllo degli errori.

#define BEGIN_TOKEN_MAP \ 
template <int n> \ 
struct add_to_ \ 
{ \ 
    static void act() {} \ 
}; \ 
std::map<std::string, void (Foo::*)()> map_; 


#define DECLARE_TOKEN(str, n) \ 
template <> struct add_to_<n> \ 
{ \ 
    static void act() { map_[#str] = &Foo::##str; add_to<n+1>::act();} \ 
};\ 
void str() 

#define END_TOKEN_MAP \ 
void init_map() { add_to_<0>::act(); } \ 
void process_token(std::string s) { (this->*map_[s])(); } 


class Foo 
{ 
    BEGIN_TOKEN_MAP 
    DECLARE_TOKEN(lt, 0) { ... } 
    DECLARE_TOKEN(gt, 1) { ... } 
    ... 
    END_TOKEN_MAP 

    Foo() { init_map(); } 
    void f(const std::string& s) { process_token(s); } 
}; 
+1

Macro e modelli misti ... Wow! Sono mezzo tentato di riprendermi il mio credito :-) – Steve314

+0

macro e template, per fare qualcosa in fase di runtime! Non sono orgoglioso di questo, ma questo è ciò che ottieni quando vuoi usare lo strumento sbagliato per il lavoro ... –

+1

Lo trovo un po 'troppo complesso solo per ottenere una bella sintassi, Penso che le versioni di ezpz o sbi sono più chiari –

5

C++ non è dinamico, quindi non c'è equivalente. Un po 'più elegante sarebbe usare una mappa e possibilmente oggetti funzionali.

12

si potrebbe usare un dispatch table come:

typedef struct { 
    char *name; 
    void (*handler)(); 
} handler_t; 

handler_t *handlers = { 
    {"lt", &lt}, 
    {"lte", &lte}, 
    {"gt", &gt}, 
    {"gte", &gte}, 
    (NULL, NULL} 
}; 

void f(const string &s) { 
    for (int i=0; handlers[i].handler; ++i) { 
     if (0 == strcmp(s.c_str(), handlers[i].name)) { 
      handlers[i].handler(); 
      return; 
     } 
    } 
} 

Vedi anche questa domanda SO: How do you implement a dispatch table in your language of choice?

+3

qual è il problema con std :: map? Stiamo parlando C++ – Nikko

+0

Non c'è alcun problema con la soluzione 'C++'. Solo un'alternativa più generale (che capita di funzionare anche in 'c'). Stavo modificando la mia risposta allo stesso tempo e non ho visto il post di Alexandre. –

+0

Io userei 'std :: pair ' invece di 'handler_t' (il' typedef' non è necessario in C++, BTW), e quindi uso 'handlers 'per inizializzare [la mappa Alexandre attivata] (http://stackoverflow.com/questions/3140683/3140711#3140711). – sbi

1

Esistono diverse possibilità. Ma la prima cosa che dovrei dire è che il C++ è fortemente tipizzato. Pertanto un metodo che gestisce un'istanza di Foo da un lato e Foo dall'altro è di un tipo diverso dal metodo che gestisce Foo e Bar.

Ora, supponiamo di voler solo gestire gli oggetti . Poi ci sono 2 soluzioni:

  • puntatori a funzione oggetti
  • funzione

L'oggetto funzione è più generale, in particolare, permetterebbe di specificare più combinazioni di parametri in un unico oggetto.

class OperatorBase 
{ 
public: 
    virtual ~OperatorBase() {} 

    bool operator()(Foo const& lhs, Foo const& rhs) const; 
    bool operator()(Foo const& lhs, Bar const& rhs) const; 
    bool operator()(Bar const& lhs, Foo const& rhs) const; 
    bool operator()(Bar const& lhs, Bar const& rhs) const; 
private: 
    // virtual methods to actually implement this 
}; 

struct LessThanOperator: OperatorBase 
{ 
    // impl 
}; 

class OperatorFactory 
{ 
public: 
    static OperatorBase& Get(std::string const& name); 

    template <class T> 
    static void Register(std::string const& name); 
private: 
    typedef boost::ptr_map<std::string, OperatorBase> ops_t; 
    static ops_t& Get() { static ops_t O; return O; } 
}; 

E poi si può procedere:

// Choose the operator 
OperatorBase& op = OperatorFactory::Get("lt"); 

Foo foo; 
Bar bar; 

bool const result = op(foo, bar); 

E 'un lavoro abbastanza noioso però.

+0

Non sono sicuro del motivo per cui questo sarebbe stato down-votato Forse perché gli operatori in "OperatorBase" sono scocciati? – sbi

+0

Non lo so, gente Al momento non fornisco spiegazioni, ma lo correggo comunque: p –

4

Seguendo il suggerimento di Alexandre C., è possibile combinare l'approccio std::map<... con un operator() per evitare di dover chiamare fino allo void Foo::f.

Ad esempio:

class Foo { 
    private: 
     map<string,void (Foo::*)()> funs; 
    public: 
     // constructors etc. 
     void operator() (const string& s) { 
     if (funs.find (s) != funs.end()) 
      (this->*funs[s])(); 
     } 
     // remainder 
}; 

E ora è possibile utilizzare foo simile a

Foo f; 
f("lt"); // calls Foo::lt() 
f("lte"); // calls Foo::lte(); 
// etc... 
0

Ci sono modi per fare le cose simili in C++ con gli array e la spedizione dinamica.

quello che fai è creare una classe astratta con una certa azione standard(), in questo modo:

class abstract_handler { 
public: 
    virtual void action() = 0; 
} 

quindi si crea sottoclassi con diverse implementazioni di action(). Ad esempio, per il ramo "FFA" si potrebbe scrivere:

class ffa_handler : public abstract_handler { 
public: 
    virtual action() { 
     // Do your custom "ffa" stuff in here 
    } 

    // Add your custom "ffa" members for action() to work on here. 
    // ...and of course a constructor to initialize them. 
} 

quindi si crea una mappa (nel tuo caso, indicizzato da std::string) di puntatori ad oggetti di ciascuna delle vostre classi. All'avvio si popola questo con gli oggetti appropriati sugli indici di stringa appropriati. Poi in fase di esecuzione tutto quello che dovete fare è:

handler_map[index_string].action();

3
// Beware, brain-compiled code ahead! 
namespace { 
    typedef std::map<std::string, void (Foo::*)()> operations_map_t; 
    typedef operations_map_t::value_type operations_entry_t; 

    const operations_entry_t* operations = { {"lt" , &Foo::lt } 
             , {"lte", &Foo::lte} 
             , {"gt" , &Foo::gt } 
             , {"gte", &Foo::gte} }; 
    const operations_map_t operations_map(operations 
             , operations + sizeof(operations) 
                /sizeof(operations[0])); 
} 

void Foo::f(const string& s) 
{ 
    operations_map_t::const_iterator it = operations_map.find(s); 
    if(it == operations_map.end()) throw "Dooh!"; 
    it->second(); 
} 
2

ho upvoted Alexandre C, ma non ho riserve circa la costruzione di una struttura di dati in fase di esecuzione (popolare lo std :: map) quando i dati sono tutti noti in fase di compilazione.

Ho svuotato the_void, ma una ricerca lineare è appropriata solo per insiemi di dati relativamente piccoli.

Un'opzione che vale la pena considerare è uno script (scritto in Python) per generare una tabella hash o un albero binario perfettamente bilanciato o qualsiasi altra cosa in fase di build. Lo farai solo se avrai necessità ricorrenti di supportare dataset di grandi dimensioni noti al momento della compilazione, naturalmente.

Probabilmente ci sono modi per fare questo in C++: sono completi di Turing e c'è almeno un generatore di modelli di stato del parser in fase di compilazione, che è chiaramente più complesso di una tabella hash o di un albero binario. Ma personalmente, non lo raccomanderei. Uno script che genera codice sarà più semplice e più robusto.

Ho uno script per generare alberi ternari, ma (1) è un po 'lungo per qui, e (2) non è esattamente un esempio brillante di buona codifica.

+0

Non è possibile specializzare un modello su stringhe in C++, quindi non sarebbe così pulito come potrebbe, ad esempio in D. Ma ci deve essere un modo intelligente per dichiarare le funzioni e popolare la tabella nello stesso punto nell'intestazione, con alcune macro [brutte] –

+0

Ho modificato la mia risposta per fornire qualcosa su quelle linee –

+0

@Alexandre C - Se sei disposto a fidarti manutentori, la più semplice soluzione di set di dati di maggiori dimensioni è probabilmente una matrice di ricerca binaria, il problema è di verificare che sia correttamente ordinato in fase di compilazione. Forse un controllo di inizializzazione in fase di esecuzione è OK, ovviamente, ma è comunque un sovraccarico. è l'a verifiche automatiche del build-time che sono la vera motivazione per l'utilizzo di uno script e dubito che si possa fare con i macro. Una volta presa la decisione di script, la generazione di strutture dati alternative non è un grosso problema. – Steve314

Problemi correlati