86

関数ポインターをコールバックとして渡す必要がある API を使用しています。クラスからこの API を使用しようとしていますが、コンパイル エラーが発生します。

コンストラクターから行ったことは次のとおりです。

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

これはコンパイルされません - 次のエラーが表示されます:

エラー 8 エラー C3867: 'CLoggersInfra::RedundencyManagerCallBack': 関数呼び出しに引数リストがありません。「&CLoggersInfra::RedundencyManagerCallBack」を使用して、メンバーへのポインターを作成します

使用する提案を試みましたが、&CLoggersInfra::RedundencyManagerCallBackうまくいきませんでした。

これに対する提案/説明はありますか??

私はVS2008を使用しています。

ありがとう!!

4

13 に答える 13

143

これは単純な質問ですが、答えは驚くほど複雑です。std::bind1st簡単な答えは、またはでやろうとしていることを実行できるということですboost::bind。長い答えは以下の通りです。

コンパイラは、を使用することを提案するのに正しいです&CLoggersInfra::RedundencyManagerCallBack。まず、RedundencyManagerCallBackがメンバー関数の場合、関数自体はクラスの特定のインスタンスに属していませんCLoggersInfra。それはクラス自体に属しています。以前に静的クラス関数を呼び出したことがある場合は、同じSomeClass::SomeMemberFunction構文を使用していることに気付いたかもしれません。関数自体は、特定のインスタンスではなくクラスに属するという意味で「静的」であるため、同じ構文を使用します。技術的に言えば、関数を直接渡さないため、「&」が必要です。関数は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の特定のインスタンスが必要です(コンパイラは何を知る必要があります」これは)。通常、「これ」を指定する方法は、構文を使用することです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つのパラメーターを受け取る関数へのポインターを必要とします。ただし&CLoggersInfra::RedundencyManagerCallBack、これは2つのパラメーター、つまり通常のパラメーターと秘密の「this」パラメーターを受け取る関数へのポインターです。そのため、まだコンパイラエラーが発生します(補足:Pythonを使用したことがある場合は、この種の混乱により、すべてのメンバー関数に「self」パラメータが必要になります)。

これを処理するための詳細な方法は、必要なインスタンスへのポインターを保持し、パラメーターを受け取る「run」や「execute」(または「()」演算子をオーバーロードする)などのメンバー関数を持つ特別なオブジェクトを作成することです。メンバー関数の場合、保存されたインスタンスのこれらのパラメーターを使用してメンバー関数を呼び出すだけです。ただし、これには、生の関数ポインターではなく、特別なオブジェクトを取得するように「Init」を変更する必要があり、Initは他の誰かのコードのように聞こえます。そして、この問題が発生するたびに特別なクラスを作成すると、コードの膨張につながります。

だから今、最後に、良い解決策、boost::bindそしてboost::function、あなたがここで見つけることができるそれぞれのドキュメント:

boost :: bind docsboost :: functiondocs

boost::bind関数とその関数へのパラメーターを取得し、そのパラメーターが所定の位置に「ロック」されている新しい関数を作成できます。したがって、2つの整数を追加する関数がある場合boost::bindは、パラメーターの1つが5にロックされている新しい関数を作成するために使用できます。この新しい関数は1つの整数パラメーターのみを取り、常に5を追加します。この手法を使用すると、「非表示の」「this」パラメーターを特定のクラスインスタンスにロックインし、必要に応じて1つのパラメーターのみを受け取る新しい関数を生成できます(非表示のパラメーターは常に最初のパラメーターであることに注意してください。そして、通常のパラメータはその後に順番に来ます)。見てboost::bind例としてドキュメントがあり、メンバー関数での使用についても具体的に説明しています。[std::bind1st][3]技術的には、同様に使用できると呼ばれる標準関数がありますが、boost::bindより一般的です。

もちろん、もう1つだけ問題があります。boost::bind素晴らしいboost::functionが作成されますが、これは技術的にはInitがおそらく望んでいるような生の関数ポインタではありません。ありがたいことに、boostは、ここのStackOverflowに記載されているように、boost::functionをrawポインターに変換する方法を提供します。これをどのように実装するかは、この回答の範囲を超えていますが、それも興味深いものです。

これがばかげて難しいように思われる場合でも心配しないでください。質問はC++の暗いコーナーのいくつかと交差しており、boost::bind一度習得すると非常に役立ちます。

C ++ 11アップデート:代わりに、boost::bind「this」をキャプチャするラムダ関数を使用できるようになりました。これは基本的に、コンパイラに同じものを生成させることです。

于 2008-12-31T05:52:30.930 に答える
68

「this」オブジェクト引数が必要なため、メンバー関数ポインターは通常の関数ポインターのように処理できないため、これは機能しません。

代わりに、次のように静的メンバー関数を渡すことができます。これは、この点で通常の非メンバー関数と同様です。

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);
}
于 2008-12-30T13:55:10.410 に答える
3

どのような議論がInit必要ですか? 新しいエラー メッセージは何ですか?

C++ のメソッド ポインターは少し使いにくいです。メソッドポインター自体に加えて、インスタンスポインターも提供する必要があります(あなたの場合はthis)。多分Initそれを別の引数として期待していますか?

于 2008-12-30T13:38:17.043 に答える
3

m_cRedundencyManagerメンバー関数を使用できますか? ほとんどのコールバックは、通常の関数または静的メンバー関数を使用するように設定されています。詳細については、C++ FAQ Lite のこのページを参照してください。

更新:m_cRedundencyManager提供した関数宣言は、次の形式の関数を期待していることを示しています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);
}
于 2008-12-30T13:47:38.443 に答える
3

クラス メンバー関数へのポインターは、関数へのポインターと同じではありません。クラス メンバーは、暗黙的な追加の引数 ( thisポインター) を取り、別の呼び出し規約を使用します。

API がメンバー以外のコールバック関数を想定している場合は、それを渡す必要があります。

于 2008-12-30T13:47:43.327 に答える
1

init には次のオーバーライドがあることがわかります。

Init(CALLBACK_FUNC_EX callback_func, void * callback_parm)

どこCALLBACK_FUNC_EXですか

typedef void (*CALLBACK_FUNC_EX)(int, void *);
于 2008-12-30T13:47:03.670 に答える
0

C++ FAQ Liteからのこの質問と回答は、あなたの質問と回答に含まれる考慮事項を非常にうまくカバーしていると思います。私がリンクしたウェブページからの短いスニペット:

しないでください。

メンバー関数はそれを呼び出すオブジェクトがなければ意味がないため、これを直接行うことはできません (X ウィンドウ システムが C++ で書き直された場合、おそらく関数へのポインターだけでなく、オブジェクトへの参照を渡します。当然、オブジェクトは必要な機能と、おそらくそれ以上の機能を具体化するでしょう)。

于 2008-12-30T13:51:34.050 に答える