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

There are 8 comments left Ir a comentario

  1. Mike Rooney /
    Usando Google Chrome Google Chrome 119.0.0.0 en Windows Windows NT

    Your blog provided us with valuable information. I am looking forward to read more blog posts from here keep it up!!Santa Claus Red Vest

  2. Andrew Mark /
    Usando Google Chrome Google Chrome 119.0.0.0 en Windows Windows NT

    This is excellent article, thank you for the share! This is what I am looking for, hope in future you will continue sharing such an superb work.
    Top Gun Bomber Jacket

  3. Jones Elizabeth /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    So luck to come across your excellent blog. Your blog brings me a great deal of fun.. Good luck with the site. gate valve manufacturer

  4. Jones elizebeth /
    Usando Google Chrome Google Chrome 121.0.0.0 en Windows Windows NT

    It was a decent post to be sure. I completely delighted in understanding it in my lunch time. Will definitely come and visit this blog all the more frequently. Much obliged for sharing. laser target shooting system

  5. Jones elizebeth /
    Usando Google Chrome Google Chrome 121.0.0.0 en Windows Windows NT

    I like this post,And I figure that they having a great time to peruse this post,they might take a decent site to make an information,thanks for sharing it to me. shilajit benefits for male

  6. Jones elizebeth /
    Usando Google Chrome Google Chrome 121.0.0.0 en Windows Windows NT

    Great job for publishing such a beneficial web site. Your web log isn’t only useful but it is additionally really creative too. There tend to be not many people who can certainly write not so simple posts that artistically. Continue the nice writing NBA即時比分

  7. Jones elizebeth /
    Usando Google Chrome Google Chrome 122.0.0.0 en Windows Windows NT

    I was surfing net and fortunately came across this site and found very interesting stuff here. Its really fun to read. I enjoyed a lot. Thanks for sharing this wonderful information. electrician near me

  8. Jones elizebeth /
    Usando Google Chrome Google Chrome 122.0.0.0 en Windows Windows NT

    I want you to thank for your time of this wonderful read!!! I definitely enjoy every little bit of it and I have you bookmarked to check out new stuff of your blog a must read blog funny onesies for baby boy

Leave a Reply