передавая функтор как указатель на функцию - PullRequest
38 голосов
/ 03 декабря 2009

Я пытаюсь использовать библиотеку C в приложении C ++ и нашел себя в следующей ситуации (я знаю свой C, но я довольно плохо знаком с C ++). На стороне C у меня есть коллекция функций, которая принимает указатель на функцию в качестве аргумента. На стороне C ++ у меня есть объекты с функтором, который имеет ту же сигнатуру, что и указатель на функцию, необходимый для функции C. Есть ли способ использовать функтор C ++ в качестве указателя на функцию для передачи функции C?

Ответы [ 10 ]

51 голосов
/ 03 декабря 2009

Вы не можете напрямую передать указатель на объект функтора C ++ как указатель функции на код C (или даже в код C ++).

Кроме того, для переноса обратного вызова в код на C его нужно как минимум объявить. как extern "C" функция, не являющаяся членом. По крайней мере, потому что некоторые API требуют определенных соглашений о вызовах функций и, таким образом, дополнительные модификаторы объявлений.

Во многих средах C и C ++ имеют одинаковые соглашения о вызовах и отличаются только в искажении имени, так что любая глобальная функция или статический член будет работать. Но вам все равно нужно заключить вызов в operator() в обычную функцию.

  • Если ваш функтор не имеет состояния (это объект только для удовлетворения некоторого формального требования и т. д.):

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

    вы можете написать обычную функцию extern "C", которая создает функтор и выполняет его оператор ().

    extern "C" int MyFunctorInC(SomeType *param)
    {
      static MyFunctor my_functor;
      return my_functor(*param);
    }
    
  • Если ваш функтор имеет состояние, например:

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

    и функция обратного вызова C принимает некоторый параметр пользовательского состояния (обычно void *):

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

    вы можете написать обычную внешнюю функцию "C", которая преобразует свой параметр состояния в Ваш функторный объект:

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

    и используйте его так:

    MyFunctor my_functor(/* setup parameters */);
    MyAlgorithmInC(input_data, MyFunctorInC, &my_functor);
    
  • В противном случае единственный нормальный способ сделать это (обычно как «без генерации машинного кода во время выполнения» и т. д.) использовать некоторое статическое (глобальное) или локальное хранилище потока для передачи функтора к внешней функции "C". Это ограничивает то, что вы можете делать со своим кодом, и уродливо, но будет работать.

7 голосов
/ 03 декабря 2009

Я нашел это " gem " с помощью Google. Видимо возможно, но я уверен, что не рекомендовал бы это. Прямая ссылка на пример исходного кода.

5 голосов
/ 03 декабря 2009

Нет, конечно. Сигнатура вашей функции C принимает аргумент в качестве функции.

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

Все трюки с функторами используются шаблоном функциями:

template<class Func>
void f (Func func)
{
    func(); // Any functor
}
3 голосов
/ 03 декабря 2009

Функция обратного вызова C, написанная на C ++, должна быть объявлена ​​как функция extern "C" - поэтому прямое использование функтора запрещено. Вам нужно написать какую-нибудь функцию-обертку, чтобы использовать ее в качестве обратного вызова, и эта обертка вызовет функтор. Конечно, протокол обратного вызова должен иметь какой-то способ передачи контекста функции, чтобы он мог добраться до функтора, иначе задача становится довольно сложной. У большинства схем обратного вызова есть способ передачи контекста, но я работал с некоторыми мёртвыми, которые этого не делают.

См. Этот ответ для получения дополнительной информации (и посмотрите в комментариях анекдотическое свидетельство того, что обратный вызов должен быть extern "C", а не просто статической функцией-членом):

2 голосов
/ 03 декабря 2009

Не думаю, что вы можете: operator() в функциональном объекте действительно является функцией-членом, и C ничего о них не знает.

То, что вы должны уметь использовать, это бесплатные функции C ++ или статические функции классов.

1 голос
/ 01 сентября 2014

GCC позволяет преобразовывать указатели на функции-члены в простые указатели на функции (первый аргумент функции, вызываемой указателем простой функции, тогда равен this).

Проверьте соответствующую ссылку в руководстве .

Для этого требуется флаг -Wno-pmf-conversions, чтобы отключить соответствующее предупреждение для явно нестандартной функции. Очень удобно для сопряжения библиотек стилей C с программированием стилей C ++. Когда указатель на функцию-член является константой, для этого даже не требуется вообще генерировать код: API все равно будет использовать этот порядок аргументов.

Если у вас уже есть функтор, то сглаживание функтора таким образом, вероятно, будет означать сглаживание его operator(), давая вам функцию, которая должна вызываться с указателем класса функтора в качестве первого аргумента. Который не обязательно помогает так много, но по крайней мере имеет связь C.

Но, по крайней мере, когда вы не проходите через функторы, это полезно и обеспечивает нормальную замену связи C для std::mem_fn из <functional>.

0 голосов
/ 03 декабря 2009

Многие C API, которые принимают обратные вызовы указателей на функции, имеют параметр void * для пользовательского состояния. Если у вас есть один из них, вам повезло - вы можете использовать функцию exterm C, которая обрабатывает пользовательские данные как своего рода ссылку или ключ для поиска функтора, а затем выполните его.

В противном случае, нет.

0 голосов
/ 03 декабря 2009

Хм, может быть, вы могли бы написать бесплатную шаблонную функцию, которая оборачивается вокруг ваших функциональных объектов. Если они все имеют одинаковую подпись, это должно работать. Как это (не проверено):

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

    return funcion_object_instance( a, b );
}

Это будет сделано для всех функций, которые принимают два целых числа и возвращают int.

0 голосов
/ 03 декабря 2009

Я бы сказал нет, потому что функтор C ++ имеет перегруженный оператор (), который является функцией-членом , и, следовательно, потребует указатель на функцию-член. Это совершенно другой тип данных, чем обычный указатель на функцию C, поскольку он не может быть вызван без экземпляра класса. Вам нужно будет передать обычную функцию или статическую функцию-член в библиотеку C. Поскольку перегруженный оператор () не может быть статическим, вы не можете этого сделать. Вам нужно передать C-библиотеку обычную функцию, не являющуюся членом или статическую функцию-член, из которой затем можно вызвать функтор C ++.

0 голосов
/ 03 декабря 2009

Это зависит от того, статический ли это метод или метод экземпляра, если он статический, то вы можете пройти через функцию как className :: functionName, если это метод экземпляра, то он более сложный, потому что вам, очевидно, нужно определенный экземпляр, но не может сделать это так же, как вы это делали бы с делегатами в C # и т. д.

Лучший способ, который я нашел для этого, - создать удерживающий класс, экземпляр которого создается с экземпляром объекта, а также с указателем на функцию. Затем удерживающий класс может вызывать функцию напрямую.

...