한 클래스에서 멤버 함수와 함께 일반 std :: function 객체 사용


169

한 클래스의 경우 하나의 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 arguments에서 xxcallobj몇 가지 이상한 템플릿 인스턴스화 오류와 함께. 현재 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

46
글로벌 캡처를 피하고 [=] [this]를 사용하여 캡처 한 것을 더 명확하게하는 것이 좋습니다 (Scott Meyers-Effective Modern C ++ 6 장. 항목 31-기본 캡처 모드 피하기)
Max Raskin

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);

이 위대한 대답에 감사드립니다.
penelope

이것은 컴파일되지만 표준입니까? 첫 번째 논증이 보장 this됩니까?
sudo rm -rf 슬래시

@ sudorm-rfslash 예, 당신은
Armen Tsirunyan

답장을 보내 주셔서 감사합니다 @ArmenTsirunyan ... 표준 에서이 정보를 어디에서 찾을 수 있습니까?
sudo rm -rf 슬래시

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_funC ++ 11에서는 더 이상 사용되지 않으며 C ++ 17에서는 제거됩니다.
Max Truxa

@Danh 그것이 정확히 내가 말하는 것입니다.;) 첫 번째 "기본"과부하는 여전히 존재합니다 : template<class R, class T> unspecified mem_fn(R T::*);.
Max Truxa

@Danh DR을 주의 깊게 읽으십시오 . 13 개의 과부하 중 12 개가 DR에 의해 제거되었습니다. 마지막 은 아니 었습니다 (C ++ 11이나 C ++ 14도 아닙니다).
Max Truxa

1
왜 다운 투표? 다른 모든 응답은 클래스 인스턴스를 바인딩해야한다고 말했습니다. 리플렉션 또는 스크립팅을위한 바인딩 시스템을 만드는 경우에는 그렇게하지 않을 것입니다. 이 대체 방법은 일부 사람들에게 유효하고 관련이 있습니다.
Greg

감사합니다 Danh, 관련 과거 및 미래 인터페이스에 대한 의견을 편집했습니다.
그렉

3

불행히도 C ++에서는 객체와 그 멤버 함수 중 하나를 참조하는 호출 가능한 객체를 직접 가져올 수 없습니다. &Foo::doSomething당신에게 멤버 함수 만 참조하는 "멤버 함수 포인터"제공 되지 관련 개체를.

이 문제를 해결하는 데는 두 가지 방법이 있습니다. 하나는 std::bind"포인터에 대한 this포인터 "를 포인터에 바인딩하는 데 사용 하는 것 입니다. 다른 하나는 this포인터 를 캡처 하고 멤버 함수를 호출 하는 람다를 사용하는 것입니다.

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

나는 후자를 선호합니다.

g ++에서 적어도 멤버 함수를 이것에 바인딩하면 크기가 3 인 객체가되고 이것을 할당하면 std::function동적 메모리 할당이 발생합니다.

반면, 캡처하는 람다 this는 크기가 하나의 포인터 일 뿐이므로 std::functiong ++을 사용하여 동적 메모리를 할당하지 않습니다.

다른 컴파일러로 이것을 확인하지는 않았지만 비슷한 결과가있을 것으로 생각됩니다.


1

후드에서 덜 일반적이고 더 정밀한 제어를 원하면 펑터를 사용할 수 있습니다. 클래스에서 다른 클래스로 API 메시지를 전달하는 내 win32 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.