6

次のように、オブジェクトとメンバー関数ポインター (コールバック) を期待するフレームワーク関数があります。

do_some_work(Object* optr, void (Object::*fptr)()); // will call (optr->*fptr)()

ラムダ式を渡すにはどうすればよいですか? 次のようなことをしたい:

class MyObject : public Object
{
    void mystuff()
    {
        do_some_work(this, [](){ /* this lambda I want to pass */ });
    }
};

すべての意味は、MyObject クラスのインターフェースをコールバックで乱雑にしないことです。

UPDdo_some_workフレームワークを制御していないため、まったく 改善できません。また、実際には 1 つの機能ではないため、何百もの機能があります。フレームワーク全体は、そのタイプのコールバックに基づいています。ラムダを使用しない一般的な使用例:

typedef void (Object::*Callback)();
class MyObject : public Object
{
    void mystuff()
    {
        do_some_work(this, (Callback)(MyClass::do_work));
    }
    void do_work()
    {
        // here the work is done
    }
};

解決策 Marcelo の回答に基づく私の解決策は次のとおりです。

class CallbackWrapper : public Object
{
    fptr fptr_;
public:
    CallbackWrapper(void (*fptr)()) : fptr_(fptr) { }
    void execute()
    {
        *fptr_();
    }
};

class MyObject : public Object
{
    void mystuff()
    {
        CallbackWrapper* do_work = new CallbackWrapper([]()
        {
           /* this lambda is passed */
        });
        do_some_work(do_work, (Callback)(CallbackWrapper::execute));
    }
};

CallbackWrapper を作成するので、コールバックが非同期で使用される場合の有効期間を制御できます。ありがとうございます。

4

4 に答える 4

6

不可能だよ。この構文(optr->*fptr)()では、fptr がメンバーへのポインターである必要があります。があなたの管理下にある場合は、またはパラメータ化do_some_workされた型など、ラムダ関数と互換性のあるものを取るように変更してください。std::function<void()>それがあなたの管理下にないレガシーフレームワークである場合、それが関数テンプレートであれば、それをラップできるかもしれません。

template <typename Object>
do_some_work(Object* optr, void (Object::*fptr)());

次に、ラッパー テンプレートを実装できます。

template <typename F>
void do_some_work(F f) {
    struct S {
        F f;
        S(F f) : f(f) { }
        void call() { f(); delete this; }
    };
    S* lamf = new S(f);
    do_some_work(lamf, &S::call);
}

class MyObject // You probably don't need this class anymore.
{
    void mystuff()
    {
        do_some_work([](){ /* Do your thing... */ });
    }
};

編集: do_some_work が非同期で完了する場合lamfは、ヒープに割り当てる必要があります。安全のために、上記のコードを適宜修正しました。これを指摘してくれた@David Rodriguezに感謝します。

于 2012-08-10T12:14:39.743 に答える
1

構文の不一致よりも、実行しようとしているアプローチにはさらに深刻な問題があります。DeadMGが示唆しているように、最良の解決策は、do_some_workある種のファンクター(std::function<void()>C ++ 11またはブースト、またはと呼ばれるジェネリックF)を使用するためのインターフェースを改善することです。operator()

Marceloが提供するソリューションは構文の不一致を解決しますが、ライブラリは最初の要素をポインターで受け取るため、コールバックの実行時にオブジェクトが有効であることを確認するのは呼び出し元の責任です。コールバックが非同期であると仮定すると、彼のソリューション(および他の同様の代替手段)の問題は、コールバックが実行される前にオブジェクトが破壊される可能性があり、未定義の動作を引き起こす可能性があることです。

何らかの形式のplimpイディオムを使用することをお勧めします。この場合の目標は、コールバックの必要性を非表示にすることです(実装の残りの部分を非表示にする必要がないため、別のクラスを使用してコールバックを処理できますが、より多くのメモリを動的に割り当てる必要がない場合は、値で保存します):

class MyClass;
class MyClassCallbacks {
   MyClass* ptr;
public:
   MyClassCallbacks( MyClass* ptr ) : ptr(ptr) {}
// callbacks that execute code on `ptr`
   void callback1() {
      // do some operations
      // update *ptr
   }
};
class MyClass {
   MyClassCallbacks callbackHandler;
public:
   void mystuff() {
      do_some_work( &callbackHandler, &MyClassHandler::callback1 );
   }
};

この設計では、2つのクラスは分離されていますが、一意の単一エンティティを表しているため、フレンド宣言を追加しMyClassCallbacksて内部データにアクセスできるようにすることは問題MyClassありません(どちらも単一のエンティティであり、よりクリーンなインターフェイスを提供するためにのみ分割されていますが、カップリングはすでに高いので、必要なカップリングを追加してfriendも問題ありません)。

MyClassとインスタンスの間には1対1の関係があるためMyClassCallbacks、それらの存続期間は制限されており、破棄中を除いて、存続期間の問題はありません。MyClass破棄中は、オブジェクトが破棄されている間に開始できるコールバックが登録されていないことを確認する必要があります。

そこにいるので、さらに一歩進んで適切な単純化を行うことをお勧めします。すべてのデータと実装を、ポインターによって保持される別のタイプに移動しMyClass、ポインターを格納し、パブリック関数のみを提供するを提供します。 、pimplオブジェクトへのフォワーダーとして実装されます。継承を使用しているため、これはやや注意が必要な場合があります。また、pimplイディオムは、型階層に実装するのが少し面倒です(拡張する必要がある場合MyClass、からの派生は、インターフェイス型ではなくpimplObjectオブジェクトで行うことができます)。

于 2012-08-10T12:52:06.467 に答える
0

私はあなたがそれを行うことができるとは思わない. あなたdo_some_work()は class のメソッドへのポインタを受け入れるように宣言されてObjectいるので、そのようなものを提供する必要があります。ラムダは のメンバーではないため、それ以外の場合optr->*fptrは無効ですObject。おそらく、クロージャーでstd::function必要なメンバーを使用および追加してみてください。Object

于 2012-08-10T12:06:13.580 に答える
-1

を使用する必要がありますstd::function<void()>。関数ポインターとメンバー関数ポインターはどちらも、コールバックにはあまり適していません。

于 2012-08-10T12:04:25.187 に答える