標準の関数ポインター+コンテキストパラダイムを使用するC-APIにコールバックを登録しようとしています。 APIは次のようになります。
void register_callback(void(*callback)(void *), void * context);
私が本当にやりたいことは、C++ラムダをコールバックとして登録できることです。さらに、ラムダは変数をキャプチャしたものにしたい(つまり、ストレートステートレスに変換できないstd::function
)
ラムダをコールバックとして登録できるようにするには、どのような種類のアダプタコードを記述する必要がありますか?
単純なアポローチは、どこかに保持されている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()>([=](){ ... }));
最も効率的な方法は、ラムダを直接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>
_にmove
dできます。
上記は寿命とデータを混合し、タイプ消去とユーティリティを組み合わせています。分割できます:
_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);
C関数ポインターとしてラムダ関数を使用する非常に簡単な方法は次のとおりです。
auto fun=+[](){ //add code here }
例については、この回答を参照してください。 https://stackoverflow.com/a/56145419/513481