2009-12-17 14 views
15

Necesito implementar un std::map con <std::string, fn_ptr> pares. Los punteros de función son punteros a métodos de la misma clase que posee el mapa. La idea es tener acceso directo a los métodos en lugar de implementar un interruptor o un equivalente.std :: map of member function punteros?

(estoy usando std::string como claves en el mapa)

estoy bastante nuevo en C++, por lo que cualquier persona podría publicar algunos pseudo-código o enlace que habla de la implementación de un mapa con punteros de función? (sugerencias de métodos pertenecientes a la misma clase que posee el mapa)

Si cree que hay un mejor enfoque para mi problema, las sugerencias también son bienvenidas.

+2

¿Cuál es el objetivo general? En la mayoría de los casos en que las personas usan interruptores, el polimorfismo es lo que deberían usar. – outis

+1

+1 reemplazar interruptores con búsquedas de tabla es algo que me gusta hacer en lenguajes dinámicos. –

+0

Tengo una clase de fábrica, que tendrá varios métodos de constructor devolviendo clases de tipo A. La idea es hacer algo como A * aClass = Factory-> newA ("key"); . Luego, la fábrica usaría la "clave" para invocar el método correcto para construir una clase A y devolverla en consecuencia. – Goles

Respuesta

24

Esto es lo más simple que se me puede ocurrir. Tenga en cuenta que no se verifican errores, y el mapa podría ser útil de manera estática.

#include <map> 
#include <iostream> 
#include <string> 
using namespace std; 

struct A { 
    typedef int (A::*MFP)(int); 
    std::map <string, MFP> fmap; 

    int f(int x) { return x + 1; } 
    int g(int x) { return x + 2; } 


    A() { 
     fmap.insert(std::make_pair("f", &A::f)); 
     fmap.insert(std::make_pair("g", &A::g)); 
    } 

    int Call(const string & s, int x) { 
     MFP fp = fmap[s]; 
     return (this->*fp)(x); 
    } 
}; 

int main() { 
    A a; 
    cout << a.Call("f", 0) << endl; 
    cout << a.Call("g", 0) << endl; 
} 
+0

Funcionó muy bien, ¡gracias !, este hilo se volvió bastante útil, ya que la implementación de plantilla publicada por @outis, también es muy buena. Marcaré esto como la respuesta a la pregunta específica, pero asegúrese de leerlo también. – Goles

+0

Oh, me olvidé de la resolución de alcance Class :: y me estaba dando errores de 'no puedo convertir argumentos'. Esto me ayudó :) – SajithP

3

Una implementación de plantilla podría quedar así:

class Factory { 
public: 
    enum which { 
     foo, bar, baz 
    }; 

    template<which w> 
    A* newA(...); 
    ... 
}; 
template<Factory::which w> 
A* Factory::newA(...) { 
    /* default implementation */ 
    throw invalid_argument(); 
} 
template<> 
A* Factory::newA<Factory::foo>(...) { 
    /* specialization for a 'foo' style A */ 
    ... 
} 
.... 

Esto requiere que el valor utilizado para determinar qué newA se llama ser conocido en tiempo de compilación. Podría utilizar un const char * como parámetro de plantilla, pero no se garantiza que funcione en todos los compiladores.

Otra opción más es crear fábricas auxiliares, una para cada método de creación de fábrica, y almacenarlas en el mapa. Esta no es una gran ventaja sobre el almacenamiento de punteros de método, pero sí le permite definir un método de creación predeterminado y simplifica la obtención de elementos del mapa (no es necesario verificar que la clave exista, porque obtendrá una fábrica predeterminada). En el lado negativo, se agregará una entrada para cada clave desconocida al mapa.

Además, si utiliza un enum en lugar de una cadena para el tipo de clave, no debería tener que preocuparse por comprobar si existe una clave en el mapa. Si bien es posible que alguien pase una clave enum no válida a newA, tendrían que lanzar explícitamente el argumento, lo que significa que no lo van a hacer por accidente. Me está costando imaginar un caso en el que alguien causaría un bloqueo intencionalmente en newA; los escenarios potenciales implican seguridad, pero un programador de aplicaciones podría bloquear la aplicación sin usar su clase.

+0

¿Me falta algo o el código anterior no funciona solo para los valores en tiempo de compilación del tipo enumerado? Si tengo una variable de enumeración de tipo "which", no puedo crear nuevas A thingies dependiendo del contenido de la variable. –

+0

@Neil: No te estás perdiendo nada. Esa es la gran limitación del enfoque de la plantilla. Puede no ser adecuado para los propósitos del Sr. Gando, pero vale la pena considerarlo. – outis

+0

En ese caso, no veo la ventaja de su enfoque sobre las funciones nombradas. –

1

Otra opción es usar delegados como opuestos a los punteros de función. This la implementación de delegados es bastante rápida, admite polimorfismos y funciona bien con contenedores STL. Usted podría tener algo como:

class MyClass { 
public: 
    // defines 
    typedef fastdelegate::FastDelegate2<int, int, int> MyDelegate; 
    typedef std::map<std::string, MyDelegate> MyMap; 

    // populate your map of delegates 
    MyClass() { 
     _myMap["plus"] = fastdelegate::MakeDelegate(this, &Plus); 
     _myMap["minus"] = fastdelegate::MakeDelegate(this, &Minus); 
    } 

    bool Do(const std::string& operation, int a, int b, int& res){ 
     MyMap::const_iterator it = _myMap.find(operation); 
     if (it != _myMap.end()){ 
      res = it.second(a,b); 
      return true; 
     } 

     return false; 
    } 
private: 
    int Plus (int a, int b) { return a+b; } 
    int Minus(int a, int b) { return a-b; } 
    MyMap _myMap;  
};  
1

Como C++ 14, podemos usar una lambda genérica para deshacerse fácilmente de punteros a métodos miembros.
Sigue un ejemplo mínimo, trabajando de una función de avance realizado con una función lambda genérica:

#include<utility> 
#include<map> 
#include<string> 
#include<iostream> 

struct SomeClass { }; 
struct SomeOtherClass { }; 

struct Test { 
    void test(SomeClass) { std::cout << "SomeClass" << std::endl; } 
    void test(SomeOtherClass) { std::cout << "SomeOtherClass" << std::endl; } 
}; 

int main() { 
    Test test; 

    auto l = [&test](auto c){ test.test(c); }; 
    std::map<std::string, decltype(l)> m; 

    m.emplace("foo", l); 
    m.emplace("bar", l); 

    m.at("foo")(SomeClass{}); 
    m.at("bar")(SomeOtherClass{}); 
} 
+0

Esto es genial y resuelve mi problema (que tiene una topología única que los demás). ¿Puedes explicar cómo funciona? En particular 'auto l = [& test] (auto c) {test.test (c); }; std :: map m; ' – Klik

+0

Espere un segundo. Llamar a 'm.at (" foo ") (SomeClass {}); m.at ("foo") (SomeOtherClass {}); 'da el mismo resultado. ¿Cuál es la diferencia entre esto y simplemente llamando a 'test.test (SomeClass {}); test.test (SomeOtherClass {}); '? – Klik

+0

[Dan resultados diferentes de hecho] (https://wandbox.org/permlink/8MEU3gr1DKbxz4Ms). – skypjack