クラスメンバー関数をコールバックとして渡すにはどうすればよいですか?
-
03-07-2019 - |
質問
コールバックとして関数ポインターを渡す必要があるAPIを使用しています。クラスからこのAPIを使用しようとしていますが、コンパイルエラーが発生しています。
これは、コンストラクタから行ったことです:
m_cRedundencyManager->Init(this->RedundencyManagerCallBack);
これはコンパイルされません-次のエラーが表示されます:
エラー8エラーC3867: 'CLoggersInfra :: RedundencyManagerCallBack':関数呼び出しに引数リストがありません。 '<!> amp; CLoggersInfra :: RedundencyManagerCallBack'を使用して、メンバーへのポインターを作成します
&CLoggersInfra::RedundencyManagerCallBack
を使用するよう提案しましたが、うまくいきませんでした。
これに関する提案/説明はありますか?
VS2008を使用しています。
ありがとう!!
解決
<!> quot; this <!> quot;を期待しているため、メンバー関数ポインターは通常の関数ポインターのように処理できないため、機能しません。オブジェクト引数。
代わりに、次のように静的メンバー関数を渡すことができます。これは、この点で通常の非メンバー関数に似ています:
m_cRedundencyManager->Init(&CLoggersInfra::Callback, this);
関数は次のように定義できます
static void Callback(int other_arg, void * this_pointer) {
CLoggersInfra * self = static_cast<CLoggersInfra*>(this_pointer);
self->RedundencyManagerCallBack(other_arg);
}
他のヒント
これは簡単な質問ですが、答えは驚くほど複雑です。簡単な答えは、std :: bind1stまたはboost :: bindでしようとしていることを実行できるということです。長い答えは以下にあります。
コンパイラは、<!> amp; CLoggersInfra :: RedundencyManagerCallBackの使用を推奨するように修正されています。まず、RedundencyManagerCallBackがメンバー関数である場合、関数自体はクラスCLoggersInfraの特定のインスタンスに属していません。クラス自体に属します。以前に静的クラス関数を呼び出したことがあれば、同じSomeClass :: SomeMemberFunction構文を使用していることに気づいたかもしれません。関数自体は特定のインスタンスではなくクラスに属するという意味で「静的」なので、同じ構文を使用します。 「<!> amp;」技術的に言えば、関数を直接渡さないためです。関数はC ++の実際のオブジェクトではありません。代わりに、技術的には関数のメモリアドレス、つまり、関数の命令がメモリ内で始まる場所へのポインタを渡します。結果は同じですが、パラメーターとして「関数を渡す」ことになります。
しかし、これはこのインスタンスの問題の半分にすぎません。先ほど言ったように、RedundencyManagerCallBack関数は特定のインスタンスに「属しません」。しかし、特定のインスタンスを念頭に置いて、コールバックとして渡したいようです。これを行う方法を理解するには、メンバー関数が実際に何であるかを理解する必要があります:追加の非表示パラメーターを持つ通常の未定義クラス関数。
例:
class A {
public:
A() : data(0) {}
void foo(int addToData) { this->data += addToData; }
int data;
};
...
A an_a_object;
an_a_object.foo(5);
A::foo(&an_a_object, 5); // This is the same as the line above!
std::cout << an_a_object.data; // Prints 10!
A :: fooはいくつのパラメーターを取りますか?通常、1と言います。しかし、内部ではfooは実際に2を取ります。A:: fooの定義を見ると、「this」ポインターが意味を持つためにAの特定のインスタンスが必要です(コンパイラーは、これは)。 「this」にしたいものを通常指定する方法は、構文MyObject.MyMemberFunction()を使用することです。しかし、これは、MyObjectのアドレスをMyMemberFunctionの最初のパラメーターとして渡すための構文上の砂糖です。同様に、クラス定義内でメンバー関数を宣言する場合、パラメーターリストに「this」を入れませんが、これは入力を節約するための言語デザイナーからの贈り物です。代わりに、追加の「this」パラメーターを自動的に取得するために、メンバー関数が静的であることを指定する必要があります。 C ++コンパイラーが上記の例をCコードに変換した場合(元のC ++コンパイラーは実際にそのように機能しました)、おそらく次のようになります。
struct A {
int data;
};
void a_init(A* to_init)
{
to_init->data = 0;
}
void a_foo(A* this, int addToData)
{
this->data += addToData;
}
...
A an_a_object;
a_init(0); // Before constructor call was implicit
a_foo(&an_a_object, 5); // Used to be an_a_object.foo(5);
例に戻ると、明らかな問題があります。 'Init'は、1つのパラメーターを受け取る関数へのポインターが必要です。しかし、<!> amp; CLoggersInfra :: RedundencyManagerCallBackは、通常のパラメーターと秘密の「this」パラメーターの2つのパラメーターを取る関数へのポインターです。したがって、コンパイラエラーが引き続き発生するのはなぜですか(補足:Pythonを使用したことがある場合、この種の混乱がすべてのメンバー関数に「自己」パラメーターが必要な理由です)。
これを処理する冗長な方法は、必要なインスタンスへのポインターを保持し、「run」や「execute」などのメンバー関数を持つ(または「()」演算子をオーバーロードする)特別なオブジェクトを作成することですメンバー関数のパラメーターを取得し、保存されたインスタンスのパラメーターでメンバー関数を呼び出すだけです。しかし、これには「Init」を変更して生の関数ポインタではなく特別なオブジェクトを取得する必要があり、Initは他の人のコードのように聞こえます。この問題が発生するたびに特別なクラスを作成すると、コードが肥大化します。
今、最後に、良い解決策、boost :: bindとboost :: function、あなたがここで見つけることができるそれぞれのドキュメント:
boost :: bind docs 、 boost :: function docs
boost :: bindを使用すると、関数とその関数のパラメーターを取得し、そのパラメーターが所定の位置に「ロック」されている新しい関数を作成できます。したがって、2つの整数を追加する関数がある場合、boost :: bindを使用して、パラメーターの1つが5にロックされる新しい関数を作成できます。この新しい関数は整数パラメーターを1つだけ受け取り、常に5を追加しますそれに。この手法を使用すると、非表示の「this」パラメーターを特定のクラスインスタンスに「ロックイン」し、必要な1つのパラメーターのみを受け取る新しい関数を生成できます(非表示パラメーターは常に最初のパラメータ、通常のパラメータが順番に来ます)。例についてはboost :: bindのドキュメントをご覧ください。メンバー関数での使用について具体的に説明しています。技術的には、std :: bind1stという標準関数も使用できますが、boost :: bindの方が一般的です。
もちろん、もう1つ問題があります。 boost :: bindは素晴らしいboost :: functionを作成しますが、これはまだ技術的にはInitが望んでいるような生の関数ポインタではありません。ありがたいことに、boostは、StackOverflow こちら。これをどのように実装するかは、この答えの範囲外ですが、興味深いことです。
これがばかげて難しいように思えても心配しないでください-あなたの質問はC ++のいくつかの暗いコーナーと交差しており、boost :: bindは一度覚えれば非常に便利です。
C ++ 11の更新:boost :: bindの代わりに、「this」をキャプチャするラムダ関数を使用できるようになりました。これは基本的にコンパイラに同じものを生成させることです。
この回答は、上記のコメントへの返信であり、VisualStudio 2008では動作しませんが、最近のコンパイラでは推奨されるはずです。
一方、ボイドポインターを使用する必要はもうありません。また、 std::bind
および std::function
が利用可能です。戻り値の型と引数はstd::placeholders
:
// std::function<return_type(list of argument_type(s))>
void Init(std::function<void(void)> f);
次に、return f("MyString");
を使用して関数ポインターを作成し、それをInitに渡すことができます:
auto cLoggersInfraInstance = CLoggersInfra();
auto callback = std::bind(&CLoggersInfra::RedundencyManagerCallBack, cLoggersInfraInstance);
Init(callback);
完全な例で、メンバー、静的メンバー、および非メンバー関数でInit
を使用する:
#include <functional>
#include <iostream>
#include <string>
class RedundencyManager // incl. Typo ;-)
{
public:
// std::function<return_type(list of argument_type(s))>
std::string Init(std::function<std::string(void)> f)
{
return f();
}
};
class CLoggersInfra
{
private:
std::string member = "Hello from non static member callback!";
public:
static std::string RedundencyManagerCallBack()
{
return "Hello from static member callback!";
}
std::string NonStaticRedundencyManagerCallBack()
{
return member;
}
};
std::string NonMemberCallBack()
{
return "Hello from non member function!";
}
int main()
{
auto instance = RedundencyManager();
auto callback1 = std::bind(&NonMemberCallBack);
std::cout << instance.Init(callback1) << "\n";
// Similar to non member function.
auto callback2 = std::bind(&CLoggersInfra::RedundencyManagerCallBack);
std::cout << instance.Init(callback2) << "\n";
// Class instance is passed to std::bind as second argument.
// (heed that I call the constructor of CLoggersInfra)
auto callback3 = std::bind(&CLoggersInfra::NonStaticRedundencyManagerCallBack,
CLoggersInfra());
std::cout << instance.Init(callback3) << "\n";
}
可能な出力:
Hello from non member function!
Hello from static member callback!
Hello from non static member callback!
さらに <=> を使用して、動的に引数を渡すことができますコールバック(たとえば、fに文字列パラメーターがある場合、<=>で<=>を使用できるようになります)。
Init
はどの引数を取りますか?新しいエラーメッセージは何ですか?
C ++のメソッドポインターは、少し使いにくいです。メソッドポインター自体に加えて、インスタンスポインターも提供する必要があります(この場合はthis
)。多分<=>は別の引数としてそれを期待しますか?
クラスメンバー関数へのポインターは、関数へのポインターと同じではありません。クラスメンバは、暗黙の追加引数( this ポインタ)を受け取り、異なる呼び出し規約を使用します。
APIが非メンバーコールバック関数を予期している場合、それを渡す必要があります。
m_cRedundencyManager
はメンバー関数を使用できますか?ほとんどのコールバックは、通常の関数または静的メンバー関数を使用するように設定されています。 C ++ FAQ Liteのこのページをご覧ください。詳細については。
更新:指定した関数宣言は、void yourCallbackFunction(int, void *)
が次の形式の関数を予期していることを示しています:void *
。したがって、この場合、メンバー関数はコールバックとして受け入れられません。静的メンバー関数は 動作する可能性がありますが、それが受け入れられない場合は、次のコードも動作します。 <=>からの邪悪なキャストを使用していることに注意してください。
// in your CLoggersInfra constructor:
m_cRedundencyManager->Init(myRedundencyManagerCallBackHandler, this);
// in your CLoggersInfra header:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr);
// in your CLoggersInfra source file:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr)
{
((CLoggersInfra *)CLoggersInfraPtr)->RedundencyManagerCallBack(i);
}
initには次のオーバーライドがあることがわかります:
Init(CALLBACK_FUNC_EX callback_func, void * callback_parm)
where CALLBACK_FUNC_EX
は
typedef void (*CALLBACK_FUNC_EX)(int, void *);
この質問と回答 a href = "https://isocpp.org/wiki/faq" rel = "nofollow noreferrer"> C ++ FAQ Lite は、あなたの質問とその答えに関連する考慮事項を非常にうまくカバーしていると思います。リンクしたWebページの短いスニペット:
Don <!>#8217; t。
メンバー関数は、呼び出すオブジェクトがなければ意味がないため それをオンにすると、<!>#8217; tこれを直接行うことができません(X Window Systemが C ++で書き直されたため、おそらくオブジェクトへの参照を渡します 関数へのポインタだけではありません。当然、オブジェクトは 必要な機能と、おそらくもっとたくさん)。
ネクロマンシング。
これまでの答えは少し不明瞭だと思います。
例を作りましょう:
ピクセルの配列(ARGB int8_t値の配列)があるとします
// A RGB image
int8_t* pixels = new int8_t[1024*768*4];
PNGを生成します。 これを行うには、関数toJpegを呼び出します
bool ok = toJpeg(writeByte, pixels, width, height);
writeByteはコールバック関数です
void writeByte(unsigned char oneByte)
{
fputc(oneByte, output);
}
ここでの問題:FILE *出力はグローバル変数でなければなりません。
マルチスレッド環境(httpサーバーなど)にいる場合は非常に悪いです。
したがって、コールバック署名を保持したまま、出力を非グローバル変数にする何らかの方法が必要です。
頭に浮かぶ直接的な解決策はクロージャーであり、メンバー関数を持つクラスを使用してエミュレートできます。
class BadIdea {
private:
FILE* m_stream;
public:
BadIdea(FILE* stream) {
this->m_stream = stream;
}
void writeByte(unsigned char oneByte){
fputc(oneByte, this->m_stream);
}
};
それから
FILE *fp = fopen(filename, "wb");
BadIdea* foobar = new BadIdea(fp);
bool ok = TooJpeg::writeJpeg(foobar->writeByte, image, width, height);
delete foobar;
fflush(fp);
fclose(fp);
ただし、期待に反して、これは機能しません。
理由は、C ++メンバー関数は、C#拡張関数のように実装されているためです。
つまり、
class/struct BadIdea
{
FILE* m_stream;
}
and
static class BadIdeaExtensions
{
public static writeByte(this BadIdea instance, unsigned char oneByte)
{
fputc(oneByte, instance->m_stream);
}
}
したがって、writeByteを呼び出すには、writeByteのアドレスだけでなく、BadIdea-instanceのアドレスも渡す必要があります。
したがって、writeByteプロシージャのtypedefがあり、次のようになっている場合
typedef void (*WRITE_ONE_BYTE)(unsigned char);
そして、次のようなwriteJpeg署名があります
bool writeJpeg(WRITE_ONE_BYTE output, uint8_t* pixels, uint32_t
width, uint32_t height))
{ ... }
(writeJpegを変更せずに)2アドレスのメンバー関数を1アドレスの関数ポインターに渡すことは基本的に不可能であり、回避する方法はありません。
C ++で実行できる次善策は、ラムダ関数を使用することです。
FILE *fp = fopen(filename, "wb");
auto lambda = [fp](unsigned char oneByte) { fputc(oneByte, fp); };
bool ok = TooJpeg::writeJpeg(lambda, image, width, height);
ただし、ラムダはインスタンスを非表示クラス(<!> quot; BadIdea <!> quot; -classなど)に渡すことと何も変わらないため、writeJpegの署名を変更する必要があります。
手動クラスに対するラムダの利点は、typedefを1つ変更するだけでよいことです
using WRITE_ONE_BYTE = std::function<void(unsigned char)>;
to
auto f = std::bind(&BadIdea::writeByte, &foobar);
そして、他のすべてをそのままにしておくことができます。
std :: bindを使用することもできます
./configure
make
make install
gcc main.c -l:libffcall.a -o ma
しかし、これは舞台裏で、ラムダ関数を作成するだけで、typedefの変更も必要です。
いいえ、静的関数ポインターを必要とするメソッドにメンバー関数を渡す方法はありません。
ただし、ソースを制御できるのであれば、ラムダは簡単な方法です。
そうでなければ、あなたは運が悪い。
C ++でできることは何もありません。
注:
std :: functionには#include <functional>
ただし、C ++ではCも使用できるため、 libffcall を使用してこれを行うことができます依存関係のリンクを気にしない場合は、プレーンCで。
GNUからlibffcallをダウンロードします(少なくともubuntuでは、配布されているパッケージは使用しないでください-壊れています)。解凍します。
#include <callback.h>
// this is the closure function to be allocated
void function (void* data, va_alist alist)
{
int abc = va_arg_int(alist);
printf("data: %08p\n", data); // hex 0x14 = 20
printf("abc: %d\n", abc);
// va_start_type(alist[, return_type]);
// arg = va_arg_type(alist[, arg_type]);
// va_return_type(alist[[, return_type], return_value]);
// va_start_int(alist);
// int r = 666;
// va_return_int(alist, r);
}
int main(int argc, char* argv[])
{
int in1 = 10;
void * data = (void*) 20;
void(*incrementer1)(int abc) = (void(*)()) alloc_callback(&function, data);
// void(*incrementer1)() can have unlimited arguments, e.g. incrementer1(123,456);
// void(*incrementer1)(int abc) starts to throw errors...
incrementer1(123);
// free_callback(callback);
return EXIT_SUCCESS;
}
main.c:
add_library(libffcall STATIC IMPORTED)
set_target_properties(libffcall PROPERTIES
IMPORTED_LOCATION /usr/local/lib/libffcall.a)
target_link_libraries(BitmapLion libffcall)
CMakeを使用する場合、add_executableの後にリンカーライブラリを追加します
target_link_libraries(BitmapLion ffcall)
または単にlibffcallを動的にリンクできます
<*>注:
libffcallヘッダーとライブラリを含めるか、libffcallの内容を使用してcmakeプロジェクトを作成することができます。