web-dev-qa-db-ja.com

関数ポインターとコンテキストを必要とするCコールバックにC ++ラムダをどのように渡すことができますか?

標準の関数ポインター+コンテキストパラダイムを使用するC-APIにコールバックを登録しようとしています。 APIは次のようになります。

void register_callback(void(*callback)(void *), void * context);

私が本当にやりたいことは、C++ラムダをコールバックとして登録できることです。さらに、ラムダは変数をキャプチャしたものにしたい(つまり、ストレートステートレスに変換できないstd::function

ラムダをコールバックとして登録できるようにするには、どのような種類のアダプタコードを記述する必要がありますか?

31
Michael Bishop

単純なアポローチは、どこかに保持されているstd::function<void()>にラムダを挿入することです。ヒープ上に割り当てられ、コールバックを受け取るエンティティに登録されたvoid*によって参照されるだけの可能性があります。コールバックは次のような関数になります。

extern "C" void invoke_function(void* ptr) {
    (*static_cast<std::function<void()>*>(ptr))();
}

std::function<S>は、空でないキャプチャを持つラムダ関数など、状態を持つ関数オブジェクトを保持できることに注意してください。次のようなコールバックを登録できます。

register_callback(&invoke_function,
  new std::function<void()>([=](){ ... }));
21
Dietmar Kühl

最も効率的な方法は、ラムダを直接voidifyすることです。

_#include <iostream>
#include <Tuple>
#include <memory>

template<typename... Args, typename Lambda>
std::pair< void(*)(void*, Args...), std::unique_ptr<void, void(*)(void*)> > voidify( Lambda&& l ) {
  typedef typename std::decay<Lambda>::type Func;
  std::unique_ptr<void, void(*)(void*)> data(
    new Func(std::forward<Lambda>(l)),
    +[](void* ptr){ delete (Func*)ptr; }
  );
  return {
    +[](void* v, Args... args)->void {
      Func* f = static_cast< Func* >(v);
      (*f)(std::forward<Args>(args)...);
    },
    std::move(data)
  };
}

void register_callback( void(*function)(void*), void * p ) {
  function(p); // to test
}
void test() {
  int x = 0;
  auto closure = [&]()->void { ++x; };
  auto voidified = voidify(closure);
  register_callback( voidified.first, voidified.second.get() );
  register_callback( voidified.first, voidified.second.get() );
  std::cout << x << "\n";
}
int main() {
  test();
}
_

ここでvoidifyはラムダと(オプションで)引数のリストを受け取り、従来のCスタイルのコールバック-_void*_ペアを生成します。 _void*_は、そのリソースが適切にクリーンアップされるように、特別な削除機能を持つ_unique_ptr_が所有しています。

_std::function_ソリューションに対するこれの利点は効率です-ランタイムインダイレクションの1つのレベルを排除しました。 voidifyによって返されるstd::unique_ptr<void, void(*)(void*)>内にあるという点で、コールバックが有効である存続期間も明確です。

より複雑なライフタイムが必要な場合は、_unique_ptr<T,D>_ sを_shared_ptr<T>_にmovedできます。


上記は寿命とデータを混合し、タイプ消去とユーティリティを組み合わせています。分割できます:

_template<typename... Args, typename Lambda>
std::pair< void(*)(void*, Args...), std::decay_t<Lambda> > voidify( Lambda&& l ) {
  typedef typename std::decay<Lambda>::type Func;
  return {
    +[](void* v, Args... args)->void {
      Func* f = static_cast< Func* >(v);
      (*f)(std::forward<Args>(args)...);
    },
    std::forward<Lambda>(l)
  };
}
_

現在、voidifyは割り当てられません。 second関数ポインターと一緒に_void*_へのポインターをfirstとして渡し、コールバックの存続期間中、voidifyを格納するだけです。

この構成をスタックから格納する必要がある場合は、ラムダを_std::function_に変換すると役立つ場合があります。または、上記の最初のバリアントを使用します。

ラムダ関数は、キャプチャ変数を持たない限り、Cコールバック関数と互換性があります。
新しい方法で古いものに新しいものを置くように強制しても意味がありません。
昔ながらの方法に従ってみませんか?

typedef struct
{
  int cap_num;
} Context_t;

int cap_num = 7;

Context_t* param = new Context_t;
param->cap_num = cap_num;   // pass capture variable
register_callback([](void* context) -> void {
    Context_t* param = (Context_t*)context;
    std::cout << "cap_num=" << param->cap_num << std::endl;
}, param);
2
Hill

C関数ポインターとしてラムダ関数を使用する非常に簡単な方法は次のとおりです。

auto fun=+[](){ //add code here }

例については、この回答を参照してください。 https://stackoverflow.com/a/56145419/513481

0
janCoffee