1つのクラスのメンバー関数でジェネリックstd :: functionオブジェクトを使用する


170

1つのクラスについて、同じクラスのメンバー関数への関数ポインターを1つのmap格納std::functionオブジェクトに格納したい。しかし、私は最初にこのコードで失敗します:

class Foo {
    public:
        void doSomething() {}
        void bindFunction() {
            // ERROR
            std::function<void(void)> f = &Foo::doSomething;
        }
};

私は受信error C2064: term does not evaluate to a function taking 0 argumentsxxcallobjいくつかの奇妙なテンプレートのインスタンスエラーと組み合わせます。現在、Visual Studio 2010/2011を搭載したWindows 8で作業していますが、VS10を搭載したWin 7では失敗します。エラーは、私が従わないいくつかの奇妙なC ++ルールに基づいている必要があります

回答:


301

非静的メンバー関数は、オブジェクトで呼び出す必要があります。つまり、常に暗黙的に「this」ポインタを引数として渡します。

std::functionシグネチャは、関数が引数(<void(void)>)を取らないことを指定しているため、最初の(そして唯一の)引数をバインドする必要あります。

std::function<void(void)> f = std::bind(&Foo::doSomething, this);

関数をパラメーターにバインドする場合は、プレースホルダーを指定する必要があります。

using namespace std::placeholders;
std::function<void(int,int)> f = std::bind(&Foo::doSomethingArgs, this, std::placeholders::_1, std::placeholders::_2);

または、コンパイラがC ++ 11ラムダをサポートしている場合:

std::function<void(int,int)> f = [=](int a, int b) {
    this->doSomethingArgs(a, b);
}

現在、C ++ 11対応のコンパイラがないので、これを確認することはできません。)


1
ブーストに依存していないので、ラムダ式を使用します;)それにもかかわらず、ありがとう!
Christian Ivicevic

3
@AlexB:Boost.BindはプレースホルダーにADLを使用せず、匿名の名前空間に配置します。
ildjarn、2011

46
私は[=]グローバル捕獲を避けるためにお勧めしますし、使用[この]撮影しているもの、それをより明確にするために(スコット・マイヤーズ-効果的な現代のC ++第6章アイテム31 -避け、デフォルトのキャプチャモード)
マックス・ラスキン氏

5
ほんの少しヒントを追加:メンバ関数ポインタが暗黙的にキャストすることができstd::function、余分なと、thisそれは最初のパラメータは次のように、だとstd::function<void(Foo*, int, int)> = &Foo::doSomethingArgs
landerlyoung

@landerlyoung:関数の名前を上記の「f」と追加して、サンプル構文を修正します。名前が必要ない場合は、mem_fn(&Foo :: doSomethingArgs)を使用できます。
Val

80

あなたが必要か

std::function<void(Foo*)> f = &Foo::doSomething;

任意のインスタンスでそれを呼び出すことができるように、または特定のインスタンスをバインドする必要があるように、例えば this

std::function<void(void)> f = std::bind(&Foo::doSomething, this);

この素晴らしい答えをありがとう:Dまさに私が必要とするもの、私はどのクラスインスタンスのメンバー関数を呼び出すためにstd :: functionを特化するかを見つけることができませんでした。
ペネロペ

これはコンパイルされますが、標準ですか?最初の引数がであることは保証されていthisますか?
sudo rm -rf slash

@ sudorm-rfslashはい、そうです
Armen Tsirunyan

返信ありがとう@ArmenTsirunyan ...標準のどこでこの情報を探すことができますか?
sudo rm -rf slash

13

クラスのインスタンスなしでメンバー関数を格納する必要がある場合は、次のようにすることができます。

class MyClass
{
public:
    void MemberFunc(int value)
    {
      //do something
    }
};

// Store member function binding
auto callable = std::mem_fn(&MyClass::MemberFunc);

// Call with late supplied 'this'
MyClass myInst;
callable(&myInst, 123);

自動なしのストレージタイプはどのようになりますか?このようなもの:

std::_Mem_fn_wrap<void,void (__cdecl TestA::*)(int),TestA,int> callable

この関数ストレージを標準の関数バインディングに渡すこともできます

std::function<void(int)> binding = std::bind(callable, &testA, std::placeholders::_1);
binding(123); // Call

過去と将来のメモ:古いインターフェイスstd :: mem_funcが存在していましたが、廃止されました。メンバー関数へのポインターを呼び出し可能にする提案がC ++ 17以降にあります。これは大歓迎です。


@Danh std::mem_fnは削除されませんでした。不必要なオーバーロードがたくさんありました。一方std::mem_fun、C ++ 11で非推奨になり、C ++ 17で削除されます。
Max Truxa、2016年

@Danhそれはまさに私が話していることです;)最初の「基本的な」オーバーロードはまだそこにあります:template<class R, class T> unspecified mem_fn(R T::*);そして、それは消えません。
マックスTruxa

@Danh DRを注意深く読んでください。13の過負荷のうち12がDRによって削除されました。最後の1つはそうではありませんでした(C ++ 11でもC ++ 14でもそうではありません)。
Max Truxa、2016年

1
なぜ反対票か。他のすべての応答は、クラスインスタンスをバインドする必要があると述べました。リフレクションまたはスクリプティング用のバインディングシステムを作成している場合は、その必要はありません。この代替方法は有効であり、一部の人々に関連しています。
グレッグ

Danhに感謝します。関連する過去および将来のインターフェースに関するコメントを編集しました。
Greg

3

残念ながら、C ++では、オブジェクトとそのメンバー関数の1つを参照する呼び出し可能オブジェクトを直接取得することはできません。関連するオブジェクトではなく&Foo::doSomethingメンバー関数を参照する「メンバー関数へのポインター」を提供します。

これには2つの方法があります。1つはstd::bind、「メンバー関数へのthisポインター」をポインターにバインドするために使用する方法です。もう1つは、thisポインターをキャプチャしてメンバー関数を呼び出すラムダを使用することです。

std::function<void(void)> f = std::bind(&Foo::doSomething, this);
std::function<void(void)> g = [this](){doSomething();};

私は後者を好みます。

g ++では、メンバー関数をこれにバインドすると、オブジェクトのサイズが3ポインターになり、これをに割り当てるstd::functionと、動的メモリ割り当てが行われます。

一方、キャプチャするラムダthisはサイズが1つのポインタにすぎstd::functionないため、それをに割り当てても、g ++では動的メモリが割り当てられません。

他のコンパイラではこれを検証していませんが、同様の結果がそこに見つかると思います。


1

フードの下で汎用性が低く、より正確な制御が必要な場合は、ファンクタを使用できます。win32 apiを使用して、クラスから別のクラスにapiメッセージを転送する例。

IListener.h

#include <windows.h>
class IListener { 
    public:
    virtual ~IListener() {}
    virtual LRESULT operator()(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
};

Listener.h

#include "IListener.h"
template <typename D> class Listener : public IListener {
    public:
    typedef LRESULT (D::*WMFuncPtr)(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 

    private:
    D* _instance;
    WMFuncPtr _wmFuncPtr; 

    public:
    virtual ~Listener() {}
    virtual LRESULT operator()(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) override {
        return (_instance->*_wmFuncPtr)(hWnd, uMsg, wParam, lParam);
    }

    Listener(D* instance, WMFuncPtr wmFuncPtr) {
        _instance = instance;
        _wmFuncPtr = wmFuncPtr;
    }
};

Dispatcher.h

#include <map>
#include "Listener.h"

class Dispatcher {
    private:
        //Storage map for message/pointers
        std::map<UINT /*WM_MESSAGE*/, IListener*> _listeners; 

    public:
        virtual ~Dispatcher() { //clear the map }

        //Return a previously registered callable funtion pointer for uMsg.
        IListener* get(UINT uMsg) {
            typename std::map<UINT, IListener*>::iterator itEvt;
            if((itEvt = _listeners.find(uMsg)) == _listeners.end()) {
                return NULL;
            }
            return itEvt->second;
        }

        //Set a member function to receive message. 
        //Example Button->add<MyClass>(WM_COMMAND, this, &MyClass::myfunc);
        template <typename D> void add(UINT uMsg, D* instance, typename Listener<D>::WMFuncPtr wmFuncPtr) {
            _listeners[uMsg] = new Listener<D>(instance, wmFuncPtr);
        }

};

使用原理

class Button {
    public:
    Dispatcher _dispatcher;
    //button window forward all received message to a listener
    LRESULT onMessage(HWND hWnd, UINT uMsg, WPARAM w, LPARAM l) {
        //to return a precise message like WM_CREATE, you have just
        //search it in the map.
        return _dispatcher[uMsg](hWnd, uMsg, w, l);
    }
};

class Myclass {
    Button _button;
    //the listener for Button messages
    LRESULT button_listener(HWND hWnd, UINT uMsg, WPARAM w, LPARAM l) {
        return 0;
    }

    //Register the listener for Button messages
    void initialize() {
        //now all message received from button are forwarded to button_listener function 
       _button._dispatcher.add(WM_CREATE, this, &Myclass::button_listener);
    }
};

幸運と知識を共有するためにすべてに感謝します。


0

std::bindこれを避けることができます:

std::function<void(void)> f = [this]-> {Foo::doSomething();}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.