Publi

Invocando métodos por su nombre (en string) con C++

Vamos a implementar una pequeña aplicación en la que el usuario pueda elegir el método de la clase que va a ejecutarse, vamos a tener una clase en la que crearemos varios métodos “ejecutables” por el usuario.
En el ejemplo que presento, aunque sea un poco repetitivo, estoy suponiendo que C++ no tiene reflexion, esto, dicho de una forma rápida es que una clase tenga la facultad de conocer sus miembros, podremos llamarlos, pero no podremos decirle que nos dé una lista. Hay formas de hacerlo, creando macros, con trucos al compilar, etc; pero para este ejemplo, veo mucho más rápido y más claro implementar un mapa con los nombres de los métodos y sus direcciones.

Creamos una clase Invoker, que será capaz de ejecutar métodos por su nombre, los métodos, supondremos que son todos de la forma:

void metodo(int numero);

Aunque sería fácil cambiarlo.

Los archivos de este post se podrán descargar al final.

Empezamos invocando métodos de la misma clase invocadora, en el siguiente ejemplo tendremos el método void hola(int), y lo podremos llamar desde invoke(). Aunque antes, sí que es necesario que la clase añada al mapa availableMethods los métodos que se podrán llamar (como comentábamos antes, la propiedad de reflexión de C++).

invoker.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* @(#)invoker.h
 */


#ifndef _INVOKER_H
#define _INVOKER_H 1

#include <string>
#include <map>

#define ADDMETHOD(class,name) this->availableMethods[#name] = &class::name;

class Invoker
{
 public:
  typedef void (Invoker::*Method)(int);

  Invoker();
  ~Invoker();

  void invoke(std::string method, int argument);
  void hola(int arg);
  void listMethods();

 private:
  std::map<std::string, Method> availableMethods;
};


#endif /* _INVOKER_H */

invoker.cpp
Aquí se hace uso de los punteros a miembro (encerrados en el mapa) para poder llamar al método cuyo nombre corresponde con la cadena recibida.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
*************************************************************
* @file invoker.cpp
*************************************************************/


#include "invoker.h"
#include <iostream>
#include <stdexcept>
#include <typeinfo>

using namespace std;

Invoker::Invoker()
{
  cout << "Inicializo invoker" << endl;
  ADDMETHOD(Invoker, hola);
}

Invoker::~Invoker()
{
}

void Invoker::invoke(std::string method, int argument)
{
  Method m = availableMethods[method];
  if (m == NULL)
    throw new runtime_error("No se encuentra el método");

  (this->*m)(argument);
}

void Invoker::listMethods()
{
  for (map<string, Method>::iterator i=availableMethods.begin(); i!=availableMethods.end(); ++i)
    {
      cout <<i->first<<endl;
    }
}

void Invoker::hola(int arg)
{
  cout << "Hola, me has dado el numero "<<arg<<endl;
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
*************************************************************
* @file main.cpp
*************************************************************/


#include <iostream>
#include "invoker.h"
#include <stdexcept>

using namespace std;

int main(int argc, char *argv[])
{
  Invoker invoker;
  cout << "Inicio del programa" << endl;
  cout << "Métodos disponibles: "<<endl;
  invoker.listMethods();

  cout << "Invoco métodos:" << endl;
  try
    {
      invoker.invoke("hola", 12);
      invoker.invoke("adios", 12);
    }
  catch (runtime_error *e)
    {
      cout << "Ha habido un problema: "<<e->what()<<endl;
    }
}

En estos tres archivos, tendremos la clase Invoker, desde la que se llamará a un método de la misma (con el prototipo especificado). Cada uno de los métodos debe estar en el mapa (extendiendo este mapa, podremos gestionar permisos de los métodos a ciertos usuarios, por ejemplo. Para añadir elementos al mapa, y hacerlo de forma un poco más fácil, se ha creado la macro ADDMETHOD(Invoker, metodo); por ahora, todos los métodos estarán en Invoker, por lo que el primer argumento se queda igual. En ese punto se hará algo como:

this->availableMethods[“nombredelmetodo”] = &Invoker::nombredelmetodo;

En este punto podemos incluir un método para comprobar el nombre, devolver una excepción cuando el método existe, añadir permisos, etc.

Pero la gracia de esto reside en que podamos tener una clase base Invoker y varias clases derivadas de ésta, en donde cada una tenga unos métodos propios. En el siguiente ejemplo, veremos cómo hemos creado una clase Controller, derivada de Invoker, en la que llamaremos a métodos propios que hemos añadido:

invoker.h
En esta clase, lo único que he añadido es el casting en la macro ADDMETHOD, ya que no estaremos tratando siempre de la misma clase, el compilador tratará Invoker::metodo() y Controller::metodo() de forma diferente, tenemos que decirle que siempre piense que son Invoker::metodo(), para no crear un tipo para cada una de las nuevas clases que creemos. Por otro lado, el typedef ahora es protected.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* @(#)invoker.h
 */


#ifndef _INVOKER_H
#define _INVOKER_H 1

#include <string>
#include <map>

#define ADDMETHOD(class,name) this->availableMethods[#name] = static_cast<Method> (&class::name);

class Invoker
{
 public:
  Invoker();
  ~Invoker();

  void invoke(std::string method, int argument);
  void listMethods();

 protected:
  typedef void (Invoker::*Method)(int);
  std::map<std::string, Method> availableMethods;
};

#endif /* _INVOKER_H */

invoker.cpp
Se ha eliminado el método hello

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
*************************************************************
* @file invoker.cpp
*************************************************************/


#include "invoker.h"
#include <iostream>
#include <stdexcept>
#include <typeinfo>

using namespace std;

Invoker::Invoker()
{
  cout << "Inicializo invoker" << endl;
}

Invoker::~Invoker()
{
}

void Invoker::invoke(std::string method, int argument)
{
  Method m = availableMethods[method];
  if (m == NULL)
    throw new runtime_error("No se encuentra el método ""+method+""");

  (this->*m)(argument);
}

void Invoker::listMethods()
{
  for (map<string, Method>::iterator i=availableMethods.begin(); i!=availableMethods.end(); ++i)
    {
      cout <<i->first<<endl;
    }
}

controller.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* @(#)controller.h
 */


#ifndef _CONTROLLER_H
#define _CONTROLLER_H 1

#include "invoker.h"

class Controller : public Invoker
{
 public:

  Controller();
  ~Controller();

  void hello(int number);
  void goodbye(int number);
  void downloadData(int number);
};

#endif /* _CONTROLLER_H */

invoker.cpp
¡¡Aquí ya tenemos total libertad!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
*************************************************************
* @file controller.cpp
*************************************************************/


#include "controller.h"
#include <iostream>

using namespace std;

Controller::Controller()
{
  ADDMETHOD(Controller, hello);
  ADDMETHOD(Controller, goodbye);
  ADDMETHOD(Controller, downloadData);
}

Controller::~Controller()
{
}

void Controller::hello(int number)
{
  cout << "Hello "<<number<<endl;
}

void Controller::goodbye(int number)
{
  cout << "Goodbye "<<number<<endl;
}

void Controller::downloadData(int number)
{
  cout << "Now I will download everything I need" << endl;
}

main.cpp
Un pequeño programa principal en donde pedimos al usuario que escriba el nombre del método que queremos cargar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
*************************************************************
* @file main.cpp
*************************************************************/


#include <iostream>
#include "controller.h"
#include <stdexcept>
#include <string>

using namespace std;

int main(int argc, char *argv[])
{
  Controller controller;
  string ask;

  cout << "Inicio del programa" << endl;
  cout << "Métodos disponibles: "<<endl;
  controller.listMethods();

  cout << "Invoco métodos:" << endl;
  do
    {
      cin >> ask;
      try
    {
      controller.invoke(ask, 12);
    }
      catch (runtime_error *e)
    {
      cout << "Ha habido un problema: "<<e->what()<<endl;
    }
    } while (ask!="goodbye");
}

Para descargar los archivos: invoca_metodosr.tar (1.6Kb)
Foto: Ju-x (Flickr) CC-by

También podría interesarte...

Leave a Reply