Использование функции-члена класса C++ в качестве функции обратного вызова C

У меня есть библиотека C, для которой требуется зарегистрировать функцию обратного вызова для настройки некоторой обработки. Тип функции обратного вызова int a(int *, int *).

Я пишу код C++, подобный следующему, и пытаюсь зарегистрировать функцию класса C++ в качестве функции обратного вызова:

class A {
  public:
   A();
   ~A();
   int e(int *k, int *j);
};

A::A()
{
   register_with_library(e)
}

int
A::e(int *k, int *e)
{
  return 0;
}

A::~A() 
{

}

Компилятор выдает следующую ошибку:

In constructor 'A::A()',
error:
 argument of type ‘int (A::)(int*, int*)’ does not match ‘int (*)(int*, int*)’.

Мои вопросы:

  1. Прежде всего, возможно ли зарегистрировать функцию члена класса С++, как я пытаюсь сделать, и если да, то как? (Я прочитал 32.8 на http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html Но на мой взгляд это не решает проблему)
  2. Есть ли альтернативный/лучший способ решить эту проблему?

person Methos    schedule 16.06.2009    source источник


Ответы (6)


Вы можете сделать это, если функция-член является статической.

Нестатические функции-члены класса A имеют неявный первый параметр типа class A*, который соответствует указателю this. Вот почему вы могли бы зарегистрировать их только в том случае, если бы сигнатура обратного вызова также имела первый параметр типа class A*.

person sharptooth    schedule 16.06.2009
comment
да. это решение сработало. Что меня смущает, так это то, что компилятор не показал ошибку int (A::)(A , int, int*)’ не соответствует ‘int ()(int, int*)’ - person Methos; 16.06.2009
comment
Так и было, но добавление (A::) означает, что функция является частью класса A, что подразумевает указатель this. - person GManNickG; 16.06.2009
comment
Мне просто любопытно... это указано в стандарте? Я только что просмотрел раздел о классах и не нашел этого. Тем не менее, очень интересно. Я просто не думаю, что каждый компилятор обязательно должен обрабатывать нестатические функции-члены таким образом. - person Tom; 16.06.2009
comment
@Methos, говоря, что функции-члены имеют неявный первый параметр, не означает, что этот параметр действительно существует. Это означает, что концептуально она есть. - person Johannes Schaub - litb; 16.06.2009
comment
@Tom, стандарт называет это неявным параметром объекта, и он имеет тип A& для неконстантных функций-членов, A const& для константных функций-членов, A volatile& для volatile... и так далее. Это ссылка, а это указатель - в основном из-за истории. Объект, для которого вызывается функция-член, называется подразумеваемым аргументом объекта. Параметр неявного объекта рассматривается как скрытый первый параметр для разрешения перегрузки, но все это только концептуально, ничего, что действительно должно быть там. - person Johannes Schaub - litb; 16.06.2009
comment
Вот почему вы могли бы зарегистрировать их только в том случае, если бы сигнатура обратного вызова также имела первый параметр типа класса A* - это было бы неопределенным поведением и не работало бы для всех видов функций-членов. - person n. 1.8e9-where's-my-share m.; 18.12.2016

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

#include <stdio.h>
#include <functional>

template <typename T>
struct Callback;

template <typename Ret, typename... Params>
struct Callback<Ret(Params...)> {
   template <typename... Args> 
   static Ret callback(Args... args) {                    
      return func(args...);  
   }
   static std::function<Ret(Params...)> func; 
};

template <typename Ret, typename... Params>
std::function<Ret(Params...)> Callback<Ret(Params...)>::func;

void register_with_library(int (*func)(int *k, int *e)) {
   int x = 0, y = 1;
   int o = func(&x, &y);
   printf("Value: %i\n", o);
}

class A {
   public:
      A();
      ~A();
      int e(int *k, int *j);
};

typedef int (*callback_t)(int*,int*);

A::A() {
   Callback<int(int*,int*)>::func = std::bind(&A::e, this, std::placeholders::_1, std::placeholders::_2);
   callback_t func = static_cast<callback_t>(Callback<int(int*,int*)>::callback);      
   register_with_library(func);      
}

int A::e(int *k, int *j) {
   return *k - *j;
}

A::~A() { }

int main() {
   A a;
}

Этот пример завершен в том смысле, что он компилирует:

g++ test.cpp -std=c++11 -o test

Вам понадобится флаг c++11. В коде вы видите, что вызывается register_with_library(func), где func — это статическая функция, динамически связанная с функцией-членом e.

person Anne van Rossum    schedule 23.04.2015
comment
Прохладный! Я всегда хотел знать, как это сделать. - person Jacko; 01.11.2015
comment
Что, если обратный вызов C имеет форму int __stdcall callback(int*, int*) ? - person Jacko; 01.11.2015
comment
@ Джеко. Ммм... речь идет о вызываемом/вызывающем абоненте, отвечающем за очистку стека, не так ли? Я не знаю... Я забыл все о Windows. :-) - person Anne van Rossum; 01.11.2015
comment
@Jacko: статический обратный вызов Ret __stdcall (аргумент arg) - person Martin.Martinsson; 14.08.2016
comment
Как бы это сделать, чтобы быть потокобезопасным? Я разместил вопрос здесь: заголовок stackoverflow.com/questions/41198854/ - person Victor.dMdB; 17.12.2016
comment
Ваш ответ высоко ценится! - person Dimfred; 13.04.2019
comment
Должно быть return func(args...); вместо func(args...); в строке 11 - person sorush-r; 17.10.2019
comment
@sorush-r Или...? С какой неудачей вы столкнулись? - person Anne van Rossum; 18.10.2019
comment
@AnnevanRossum Ошибка компиляции, жалующаяся на то, что не возвращается значение из непустой функции. - person sorush-r; 18.10.2019
comment
Ах, я этого не видел (только что проверил еще раз с помощью gcc). Хотя это не больно. :-) Поправлю пост. - person Anne van Rossum; 18.10.2019
comment
@AnnevanRossum Хорошая работа. - person sonu gupta; 12.02.2021
comment
@AnnevanRossum, ваше решение отличное, но я столкнулся с проблемой создания двух таких обратных вызовов, а второй переопределяет первый. Я написал на stackoverflow.com/q/66474621/2725742 о том, какое минимальное изменение необходимо было бы иметь для разделения статических оболочек нравится. - person user2725742; 04.03.2021
comment
@user2725742 user2725742 Ваше решение по добавлению дополнительного параметра шаблона действительно является минимальным изменением, которое я бы рекомендовал. - person Anne van Rossum; 05.03.2021

Проблема в том, что метод != функция. Компилятор преобразует ваш метод во что-то вроде этого:

int e( A *this, int *k, int *j );

Итак, вы точно не сможете его передать, потому что экземпляр класса нельзя передать в качестве аргумента. Один из способов обойти это сделать метод статическим, таким образом, он будет иметь правильный тип. Но это будет не экземпляр класса, а доступ к нестатическим членам класса.

Другой способ — объявить функцию со статическим указателем на A, инициализированным в первый раз. Функция только перенаправляет вызов в класс:

int callback( int *j, int *k )
{
    static A  *obj = new A();
    a->(j, k);
}

Затем вы можете зарегистрировать функцию обратного вызова.

person Raoul Supercopter    schedule 16.06.2009
comment
Что такое «метод» в C++? Это слово ни разу не встречается в стандарте C++. - person Aconcagua; 13.04.2019
comment
@Aconcagua, я думаю, вы знаете, но вот ответ на ваш вопрос: stackoverflow.com/questions/8596461/ - person Alexis Wilke; 30.04.2019
comment
Функциональный член (метод) определенно является функцией. Тот факт, что есть (действительно) дополнительный параметр, не делает его нефункциональным объектом. - person Alexis Wilke; 30.04.2019
comment
@AlexisWilke Гораздо важнее первые два комментария к упомянутому ответу. Кроме того, второй абзац (взаимозаменяемость) подразумевает функцию != функция. На первый взгляд это может показаться странным, но мне пришлось пройти трудный путь (небольшие недоразумения, ведущие к серьезным ошибкам), насколько важны четкие определения. Итак, выводим два важных правила: 1. Не используйте терминологию, которая не имеет четкого определения! 2. Не используйте новые определения параллельно с существующими. - person Aconcagua; 01.05.2019
comment
В данном случае нарушаются даже оба правила, поскольку уже есть «функция-член», а «метод даже не определен четко в C++». Теперь это не программное обеспечение, но все же пример того, что может произойти, если терминология не ясна (достаточно ). Примечание: впоследствии терминология была стандартизирована с четко различимыми формулировками... - person Aconcagua; 01.05.2019
comment
В a->(j, k); вы пропустили ввод e? - person Alexis Wilke; 02.05.2019

Ну... если вы работаете на платформе win32, всегда есть неприятный способ Thunking...

Thunking в Win32: упрощение обратных вызовов для нестатических функций-членов

Это решение, но я не рекомендую его использовать.
У него есть хорошее объяснение, и приятно знать, что оно существует.

person TimW    schedule 16.06.2009

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

Проще всего было бы сделать следующее:

//In a header file:
extern "C" int e(int * k, int * e);

//In your implementation: 
int e(int * k, int * e) { return 0; }
person PaulJWilliams    schedule 16.06.2009
comment
то есть вы имеете в виду не делать это функцией-членом? - person Methos; 16.06.2009
comment
В данном случае да. IMO большая простота, обеспечиваемая использованием автономной функции, перевешивает отсутствие задействованной инкапсуляции. - person PaulJWilliams; 16.06.2009
comment
Это предполагает, что его функция e не требует доступа к this. - person Alexis Wilke; 30.04.2019

В этом решении у нас есть класс шаблона со статическим методом, который будет передан «функции c» в качестве обратного вызова. Этот класс содержит «обычный» объект (с функцией-членом с именем callback(), которая будет наконец вызвана).

Как только ваш класс (здесь A) определен, его можно легко использовать:

int main() {

  Holder<A> o ( A(23, 23) );

  std::cout << o().getN() << "\n";

  callACFunctionPtr( fun );

  callACFunctionPtr( o.callback );

} // ()

Полный пример:

#include <iostream>

// ----------------------------------------------------------
// library class: Holder
// ----------------------------------------------------------
template< typename HeldObjectType >
class Holder {
public:
  static inline HeldObjectType object;

  static void callback( ) {
    object.callback();
  } // ()

  HeldObjectType &  operator() ( ) {
    return object;
  }

  Holder( HeldObjectType && obj )
  {
    object = obj;
  }

  Holder() = delete;

}; // class

// ----------------------------------------------------------
// "old" C function receivin a ptr to function as a callback
// ----------------------------------------------------------
using Callback = void (*) (void);

// ..........................................................
// ..........................................................
void callACFunctionPtr( Callback f ) {
  f();
} // ()

// ----------------------------------------------------------
// ----------------------------------------------------------
void fun() {
  std::cout << "I'm fun\n";
} // 

// ----------------------------------------------------------
// 
// Common class where we want to write the
// callback to be called from callACFunctionPtr.
// Name this function: callback
// 
// ----------------------------------------------------------
class A {
private:
  int n;

public:

  A(  ) : n( 0 ) { }

  A( int a, int b ) : n( a+b ) { }

  void callback( ) {
    std::cout << "A's callback(): " << n << "\n";
  }

  int getN() {
    return n;
  }

}; // class

// ----------------------------------------------------------
// ----------------------------------------------------------
int main() {

  Holder<A> o ( A(23, 23) );

  std::cout << o().getN() << "\n";

  callACFunctionPtr( fun );

  callACFunctionPtr( o.callback );

} // ()
person cibercitizen1    schedule 10.07.2019