2010-06-29 4 views
14

Este es mi código (simplificación de un problema de la vida real):¿Cuál es una buena alternativa a esta construcción fea, en 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 */ } 
}; 

Ésta es la forma en que lo haría en PHP/Python/JavaScript/muchos otros idiomas (ejemplo en PHP):

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

¿Cómo puedo hacer que mi código de C++ sea tan elegante como este ejemplo de PHP? Gracias por adelantado.

+5

¿Qué pasaría en PHP si el argumento $ s es incorrecto? – Arseny

+3

se puede agregar una prueba, pero creo que la pregunta no es acerca de lo que se puede agregar para evitar la situación en el código PHP – ShinTakezou

+0

¿Por qué no utilizar un interruptor? –

Respuesta

37

No hay reflejo en C++. Sin embargo, algo así como un std::map<std::string, void (Foo::*)()> debería hacer el truco.


EDITAR: Aquí hay un código feo para hacerlo de forma sostenible. Tenga en cuenta lo siguiente:

  • Esto probablemente se puede mejorar de forma diferentes
  • Por favor agregar el código para hacer frente a las fichas que no existen. No hice ningún error al verificar.

#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

Macros y plantillas mezcladas ... ¡Guau! Estoy medio tentado de recuperar mi upvote ;-) – Steve314

+0

macros y plantillas, para hacer algo en tiempo de ejecución. No estoy orgulloso de esto, pero esto es lo que obtienes cuando quieres usar la herramienta incorrecta para el trabajo ... –

+1

Me resulta un poco complejo para obtener una sintaxis agradable, Creo que las versiones de ezpz o sbi están más claras –

5

C++ no es dinámica, lo que no hay equivalente exacto. Un poco más elegante sería usar un mapa y posiblemente objetos funcionales.

12

Se puede usar un dispatch table como:

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; 
     } 
    } 
} 

Ver también esta cuestión de forma: How do you implement a dispatch table in your language of choice?

+3

¿cuál es el problema con std :: map? Estamos hablando de C++ – Nikko

+0

No hay problema con la solución 'C++'. Solo una alternativa más general (que también funciona en 'c'). Estaba editando mi respuesta al mismo tiempo y no vi la publicación de Alexandre. –

+0

Usaría 'std :: pair ' en lugar de 'handler_t' (el' typedef' no es necesario en C++, BTW), y luego usar 'handlers 'para inicializar [el mapa que trajo Alexandre] (http://stackoverflow.com/questions/3140683/3140711#3140711). – sbi

1

Tiene varias posibilidades. Pero lo primero que debería decir es que C++ está fuertemente tipado. Por lo tanto, un método que maneja una instancia de Foo por un lado y Foo por otro lado es de un tipo diferente del método que maneja Foo y Bar.

Supongamos que solo desea manejar objetos Foo. Entonces usted tiene 2 soluciones:

  • punteros de función objetos
  • función

El objeto de la función es más general, sobre todo, sería permitirá especificar múltiples combinaciones de parámetros en un solo objeto.

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; } 
}; 

Y entonces se puede proceder:

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

Foo foo; 
Bar bar; 

bool const result = op(foo, bar); 

Es un trabajo bastante tedioso sin embargo.

+0

No estoy seguro de por qué se votaría negativamente. Tal vez porque los operadores en' OperatorBase' están equivocados? – sbi

+0

No lo sé, la gente no dé razones en estos días. Pero lo arreglaré de todos modos: p –

4

Siguiendo con la sugerencia de Alexandre C., puede combinar el enfoque std::map<... con un operator() para evitar tener que llamar al void Foo::f.

Por ejemplo:

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 
}; 

Y ahora se puede utilizar foo similar a

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

Hay maneras de hacer cosas similares en C++ con matrices y envío dinámico.

Lo que se hace es crear una clase abstracta con un poco de acción estándar(), así:

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

a continuación, crear subclases con diferentes implementaciones de action(). Por ejemplo, para su rama "FFA", se podría escribir:

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. 
} 

a continuación, crear un mapa (en su caso, un índice por std::string) de punteros a objetos de cada una de sus clases. Al inicio, rellene esto con los objetos adecuados en los índices de cadena correctos. A continuación, en tiempo de ejecución todo lo que tiene que hacer es:

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

He upvoted Alexandre C, pero tengo reservas en cuanto a la construcción de una estructura de datos en tiempo de ejecución (poblar el std :: mapa) cuando todos los datos son conocidos en tiempo de compilación.

He votado hacia arriba the_void, pero una búsqueda lineal solo es apropiada para conjuntos de datos relativamente pequeños.

Una opción que vale la pena considerar es un script (escrito en, por ejemplo, Python) para generar un hash-table o un árbol binario perfectamente equilibrado o lo que sea en tiempo de compilación. Solo lo hará si tiene una necesidad recurrente de admitir grandes conjuntos de datos conocidos en tiempo de compilación, por supuesto.

Probablemente hay maneras plantilla-engaño para hacer esto en C++ - se Turing completo, y theres al menos un generador de modelo de estado del analizador en tiempo de compilación, que es claramente más complejo que una tabla hash o un árbol binario. Pero personalmente, no lo recomendaría. Un script de generación de código será más simple y más robusto.

Tengo un script para generar árboles ternarios, pero (1) es un poco largo aquí, y (2) no es exactamente un brillante ejemplo de buena codificación.

+0

No puede especializar una plantilla en cadenas en C++, así que esto no sería tan limpio como podría, por ejemplo en D. Pero debe haber una forma ingeniosa de declarar las funciones y completar la tabla en el mismo lugar en el encabezado, con algunas macros [feas]. –

+0

He editado mi respuesta para proporcionar algo similar a esas líneas. –

+0

@Alexandre C - Si estás dispuesto a confiar mantenedores, la solución de dataset más grande más grande es probablemente una matriz de búsqueda binaria. El problema es validar que está ordenada correctamente en tiempo de compilación. Tal vez una verificación de inicialización en tiempo de ejecución sea correcta, por supuesto, pero sigue siendo una sobrecarga. es el a comprobaciones de tiempo de compilación eliminadas que son la verdadera motivación para usar un script, y dudo que se pueda hacer con macros. Una vez que se toma la decisión de guiones, generar estructuras de datos alternativas no es tan importante. – Steve314

Cuestiones relacionadas