Перенос C-функции со статическим параметром обратного вызова в C++- функцию, которая принимает закрытый член в качестве обратного вызова

Я хочу обернуть эту C-функцию,

int sqlite3_exec(
  sqlite3*,                                  /* An open database */
  const char *sql,                           /* SQL to be evaluated */
  int (*callback)(void*,int,char**,char**),  /* Callback function */
  void *,                                    /* 1st argument to callback */
  char **errmsg                              /* Error msg written here */
);

в функции C++, как,

namespace
IronMaiden
{
  int
  BookOfSouls::MyWrapper( DB* db
  , std::string& sql
  , ?? callback function ?? int (*callback)(void*,int,char**,char**) ?? )
  {
    .
    .
    .
    sqlite3_exec( db->get(), sql.c_str(), ?? callback ??, nullptr, nullptr);
    .
    .
    .
  }
}

Я могу передать любую статическую функцию sqlite3_exec(), но я хочу передать приватную функцию-член из BookOfSouls объект в качестве функции обратного вызова, и я хочу получить доступ к частным данным объекта из этой функции.

2 ответа

Решение

Итак, после некоторых испытаний и неудач я получил способ ТОЧНО достичь того, что я хочу сделать.

Мое решение не простое... мои страхи сбываются...

Потратив несколько часов, чтобы ползти по SO и Интернету (как в темном, холодном и жутком подземелье, с моим светлым факелом и музыкой Angelscourge), я пришел к выводу, что нет такой вещи, как прямой путь!

         /*...I can't blame a C lib to don't support some C++ feature...*/

Итак, мне нужно было найти другую стратегию... так что я интенсивно использовал свой мозг (иначе хедбэнг)... там выскочило решение:

          /* The solution uses `libsigc++` AND a static function. */

Статическая функция находится в пространстве имен оболочки. Эта статическая функция сделает одно, испустит сигнал. так что если я возьму тот же пример, что и в вопросе,

namespace
IronMaiden
{
  sigc::signal<int,void*,int,char**,char**> signalCB; // `extern` is a friend
  .
  .
  .
  static int 
  callback( void *PersonalUse
          , int argc
          , char **argv
          , char **azColName )
  {
    return IronMaiden::signalCB.emit( PersonalUse, argc, argv, azColName);

  }
  .
  .
  .
  int
  BookOfSouls::MyWrapper( DB* db
                        , std::string& sql
                        , const sigc::slot<int,void*,int,char**,char**>& slotCB )
  {
    .
    .
    .
    sigc::connection signalConnection = IronMaiden::signalCB.connect( slotCB );
    sqlite3_exec( db->get(), sql.c_str(), callback, nullptr, nullptr);
    signalConnection.disconnect();
    .
    .
    .
  }
}

Чтобы подключить функцию (приватный член), скажем YearOfTheGoat::ImTheCallbackFunction(), из YearOfTheGoat объект,

YearOfTheGoat::ImSomeFunctionFromWhereIWantToUseMyWrapper()
{
  IronMaiden::MyWrapper( DBobj, transaction
                 , sigc::mem_fun(*this, &YearOfTheGoat::ImTheCallbackFunction) );
}

так что теперь каждый раз, когда sqlite3_exec() звонки callback(), что издаст сигнал, затем вызовет ImTheCallbackFunction()

некоторые важные заметки:

  • в этом примере параметры и тип возвращаемого значения ImTheCallbackFunction() должны быть идентичны тем callback(),
  • будьте осторожны, чтобы отключиться или сигнал останется подключенным.
  • У меня пока нет проблем с этим решением. Но вы должны понимать, что я написал этот ответ сразу после того, как написал код в своей программе. Так что я не собираюсь утверждать, что это ЛУЧШЕЕ решение. Но это работает!
  • заметка о sqlite3_exec Кажется, что функция cb вызывается только в том случае, если есть, что возвращать.
  • не путайся между BookOfSouls а также YearOfTheGoat, Первое НЕ МАТЕРИАЛЬНО. Во-вторых, я использовал объект. (NB с BookOfSouls не является нематериальным, движущимся callback а также sigc::signal внутри BookOfSouls неплохая идея).

комментарии очень приветствуются

Ключ является void* аргумент:

int (*callback)(void*,int,char**,char**),  /* Callback function */
void *,                                    /* 1st argument to callback */

Обратный вызов является бесплатной функцией, но вы можете предоставить произвольный первый аргумент в. Поэтому, если вы хотите передать функцию-член, мы можем воспользоваться тем фактом, что лямбда без захвата может быть преобразована в указатель на функцию (благодаря некоторому колдовству):

template <typename F, int (F::*mem)(int, char**, char**)>
void set_callback(F* f) {
        sqlite3_exec(db->get(), sql.c_str(), 
            +[](void* arg, int i, char** p, char**q){
                return (static_cast<F*>(arg)->*mem)(i, p, q);
            },
            f,
            nullptr, nullptr);
}

Обратите внимание, что функция-член также должна быть аргументом шаблона, чтобы лямбда не захватывала. Было бы проще, если бы вам просто потребовалось конкретное имя функции, например sqlCallback()тогда ваша функция обратного вызова будет:

+[](void* arg, int i, char** p, char** q) {
    return static_cast<F*>(arg)->sqlCallback(i, p, q);
}
Другие вопросы по тегам