pasar el funtor como puntero de función


Estoy tratando de usar una biblioteca de C en una aplicación de C++ y me he encontrado en la siguiente situación (conozco mi C, pero soy bastante nuevo en C++). En el lado C tengo una colección de funciones que toma un puntero de función como su argumento. En el lado de C++ tengo objetos con un funtor que tiene la misma firma que el puntero de función que necesita la función C. ¿Hay alguna manera de usar el funtor C++ como puntero de función para pasar a la función C?

 37
Author: dagw, 2009-12-03

10 answers

No se puede pasar directamente un puntero a un objeto funtor de C++ como un puntero de función al código de C (o incluso al código C++).

Además, para pasar portablemente una devolución de llamada al código C, debe ser declarado al menos como función extern "C" no miembro. Al menos, porque algunas API requieren convenciones de llamada a funciones específicas y, por lo tanto, modificadores de declaración adicionales.

En muchos entornos, C y C++ tienen las mismas convenciones de llamada y solo difieren en nombre mangling, por lo que cualquier función global o miembro estático funcionará. Pero todavía necesita envolver la llamada a operator() en una función normal.

  • Si su funtor no tiene estado (es un objeto solo para satisfacer alguna requisitos etc):

    class MyFunctor {
      // no state
     public:
      MyFunctor();
      int operator()(SomeType &param) const;
    }
    

    Puede escribir una función externa normal "C" que crea el funtor y ejecuta su operador().

    extern "C" int MyFunctorInC(SomeType *param)
    {
      static MyFunctor my_functor;
      return my_functor(*param);
    }
    
  • Si su funtor tiene estado, por ejemplo:

    class MyFunctor {
      // Some fields here;
     public:
      MyFunctor(/* some parameters to set state */);
      int operator()(SomeType &param) const;
      // + some methods to retrieve result.
    }
    

    Y la función de devolución de llamada C toma algún tipo de parámetro de estado de usuario (generalmente void*):

    void MyAlgorithmInC(SomeType *arr,
                        int (*fun)(SomeType *, void *),
                        void *user_state);
    

    Puede escribir una función externa normal "C" que arroja su parámetro de estado a su objeto functor:

    extern "C" int MyFunctorInC(SomeType *param, void *user_state)
    {
      MyFunctor *my_functor = (MyFunctor *)user_state;
      return (*my_functor)(*param);
    }
    

    Y úsalo así:

    MyFunctor my_functor(/* setup parameters */);
    MyAlgorithmInC(input_data, MyFunctorInC, &my_functor);
    
  • De lo contrario, la única forma normal de hacerlo (normal como en "sin generar código de máquina en tiempo de ejecución", etc.) es usar algún almacenamiento local estático (global) o de hilo para pasar el funtor a una función externa "C". Esto limita lo que puede hacer con su código y es feo, pero funcionará.

 50
Author: Tomek Szpakowicz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-12 07:17:01

Encontré esta " gema" usando Google. Aparentemente posible, pero seguro que no lo recomendaría. Direct enlace al código fuente de ejemplo.

 7
Author: Andreas Brinck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-03 14:11:29

No, por supuesto. La firma de su función C toma un argumento como función.

void f(void (*func)())
{
  func(); // Only void f1(), void F2(), ....
}

Todos los trucos con funtores son utilizados por funciones de plantilla :

template<class Func>
void f (Func func)
{
    func(); // Any functor
}
 5
Author: Alexey Malistov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-24 12:45:19

Una función callback C escrita en C++ debe ser declarada como una función extern "C" - así que usar un funtor directamente está fuera. Necesitará escribir algún tipo de función de envoltura para usarla como devolución de llamada y hacer que la envoltura llame al funtor. Por supuesto, el protocolo de devolución de llamada necesitará tener alguna forma de pasar el contexto a la función para que pueda llegar al funtor, o la tarea se vuelve bastante complicada. La mayoría de los esquemas de devolución de llamada tienen una forma de pasar el contexto, pero he trabajado con algunos con muerte cerebral que no lo hagas.

Vea esta respuesta para más detalles (y busque en los comentarios evidencia anecdótica de que la devolución de llamada debe ser extern "C" y no solo una función miembro estática):

 3
Author: Michael Burr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:00:12

No creo que puedas: operator() en una función el objeto es realmente una función miembro, y C no sabe nada de eso.

Lo que debería ser capaz de usar son funciones libres de C++, o funciones estáticas de clases.

 2
Author: UncleBens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-03 14:08:21

GCC le permite convertir los punteros de función miembro en punteros de función simples (el primer argumento de la función llamada por el puntero de función simple es this).

Echa un vistazo al enlace respectivo en el manual.

Esto requiere la bandera -Wno-pmf-conversions para silenciar la advertencia respectiva para la característica decididamente no estándar. Muy conveniente para interconectar bibliotecas de estilo C con programación de estilo C++. Cuando el puntero de la función miembro es una constante, esto ni siquiera necesita generar ningún código: la API usaría ese orden de argumentos de todos modos.

Si ya tiene un funtor, aplanar el funtor de esa manera probablemente significará aplanar su operator(), dándole una función que tiene que ser llamada con un puntero de clase funtor como su primer argumento. Lo que no necesariamente ayuda mucho, pero al menos tiene vinculación C.

Pero al menos cuando usted no está pasando por functors esto es útil y proporciona una sensatez C sustitución del enlace para std::mem_fn de <functional>.

 1
Author: user3996901,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-01 10:43:48

Depende si este es un método estático o de instancia, si es estático entonces puede pasar a través de la función como className::functionName, si es un método de instancia es bastante más complicado, porque obviamente necesita vincularse a una determinada instancia, pero no puede hacerlo de la misma manera que lo haría con los delegados en C#, etc.

La mejor manera que he encontrado de hacer esto es crear una clase holding que se instancie con la instancia del objeto, así como el puntero de la función, el holding class puede entonces invocar la función directamente.

 0
Author: Tim Ebenezer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-03 14:08:24

Diría que no, porque un funtor de C++ tiene un operador sobrecargado () que es una función miembro , y por lo tanto requeriría un puntero de función miembro. Este es un tipo de datos totalmente diferente a un puntero de función C normal, ya que no se puede invocar sin una instancia de la clase. Tendría que pasar una función normal o una función miembro estática a la biblioteca C. Dado que un operador () sobrecargado no puede ser estático, no puede hacerlo. Tendrías que pasar la C-biblioteca un normal, función no miembro o función miembro estática, desde la que puede invocar el funtor de C++.

 0
Author: Charles Salvia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-03 14:09:16

Hm, tal vez usted podría escribir una función de plantilla libre que envuelve alrededor de su función-objetos. Si todos tienen la misma firma, esto debería funcionar. Así (no probado):

template<class T>
int function_wrapper(int a, int b) {
    T function_object_instance;

    return funcion_object_instance( a, b );
}

Esto haría para todas las funciones que toman dos ints y devuelven un int.

 0
Author: Björn Pollex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-03 14:10:06

Muchas API de C que reciben devoluciones de llamada de puntero de función tienen un parámetro void* para el estado del usuario. Si tienes uno de esos, estás de suerte: puedes usar una función extérm C que trata los datos del usuario como algún tipo de referencia o clave para buscar el funtor y luego ejecutarlo.

De lo contrario, no.

 0
Author: Joe Gauterin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-03 16:22:43