클래스 멤버를 사용한 C ++ 콜백


90

나는 이것이 여러 번 요청되었다는 것을 알고 있으며, 그 때문에 균열을 파헤 치고 작동하는 간단한 예를 찾기가 어렵습니다.

나는 이것을 가지고 있으며, 간단하고 작동합니다 MyClass...

#include <iostream>
using std::cout;
using std::endl;

class MyClass
{
    public:
        MyClass();
        static void Callback(MyClass* instance, int x);
    private:
        int private_x;
};

class EventHandler
{
    public:
        void addHandler(MyClass* owner)
        {
            cout << "Handler added..." << endl;
            //Let's pretend an event just occured
            owner->Callback(owner,1);
        }
};

EventHandler* handler;

MyClass::MyClass()
{
    private_x = 5;
    handler->addHandler(this);
}

void MyClass::Callback(MyClass* instance, int x)
{
    cout << x + instance->private_x << endl;
}

int main(int argc, char** argv)
{
    handler = new EventHandler();
    MyClass* myClass = new MyClass();
}

class YourClass
{
    public:
        YourClass();
        static void Callback(YourClass* instance, int x);
};

다시 작성 EventHandler::addHandler()하면 MyClassYourClass. 미안하지만 뇌가 작동하는 방식 일뿐입니다. 작동 원리와 작동 방식을 이해하기 전에 작동하는 간단한 예를 봐야합니다. 이 작업을 할 수있는 가장 좋은 방법이 있다면 지금이 그것을 보여줄 때입니다. 코드를 마크 업하고 다시 게시하십시오.

[편집하다]

답변을 받았지만 확인 표시를하기 전에 답변이 삭제되었습니다. 제 경우의 대답은 템플릿 함수였습니다. addHandler를 이것으로 변경했습니다 ...

class EventHandler
{
    public:
        template<typename T>
        void addHandler(T* owner)
        {
            cout << "Handler added..." << endl;
            //Let's pretend an event just occured
            owner->Callback(owner,1);
        }
};

4
누가 템플릿 함수 예제를 게시 했습니까? 체크 표시를 받았지만 테스트하는 동안 답변을 삭제했습니다. 내가 필요한 일을 정확히했습니다. 내가 읽고 있던 다른 모든 정보의 스튜에서 간단한 함수 템플릿이 사라졌습니다. 질문에 대한 수정으로 답변이 추가되었습니다.
BentFX 2013 년

JaredC라고 생각합니다. 당신은 = 그를 사냥 P해야 할 수도 있습니다
WhozCraig

답변:


182

정적 메서드를 사용하고 클래스 인스턴스에 대한 포인터를 전달하는 대신 새로운 C ++ 11 표준의 기능을 사용할 수 있습니다. std::functionand std::bind:

#include <functional>
class EventHandler
{
    public:
        void addHandler(std::function<void(int)> callback)
        {
            cout << "Handler added..." << endl;
            // Let's pretend an event just occured
            callback(1);
        }
};

이제 addHandler메서드는 std::function인수를 받아들이고이 "함수 개체"에는 반환 값이 없으며 정수를 인수로받습니다.

특정 함수에 바인딩하려면 다음을 사용합니다 std::bind.

class MyClass
{
    public:
        MyClass();

        // Note: No longer marked `static`, and only takes the actual argument
        void Callback(int x);
    private:
        int private_x;
};

MyClass::MyClass()
{
    using namespace std::placeholders; // for `_1`

    private_x = 5;
    handler->addHandler(std::bind(&MyClass::Callback, this, _1));
}

void MyClass::Callback(int x)
{
    // No longer needs an explicit `instance` argument,
    // as `this` is set up properly
    cout << x + private_x << endl;
}

std::bind명시 적으로 암시 적 this포인터를 인수로 지정해야하므로 핸들러를 추가 할 때 사용해야 합니다. 독립형 기능이있는 경우 다음을 사용할 필요가 없습니다 std::bind.

void freeStandingCallback(int x)
{
    // ...
}

int main()
{
    // ...
    handler->addHandler(freeStandingCallback);
}

이벤트 핸들러가 std::function객체를 사용하면 새로운 C ++ 11 람다 함수 를 사용할 수 있습니다 .

handler->addHandler([](int x) { std::cout << "x is " << x << '\n'; });

4
감사합니다 Joachim! 이 예제는 std :: function 및 std :: bind를 이해하기 위해 많은 작업을 수행합니다. 앞으로도 꼭 사용하겠습니다! 편집 나는 아직도 모든 :)에서 람다를하지 않습니다
BentFX

3
나는 이것을 나의 큰 프로젝트 (약 6,000 줄)로 접었다. 그것은 다른 콜백과 매개 변수를 가진 버튼 정의의 벡터를 사용하고 그것을 wxWidget에 공급한다. 그래서 객체는 wxFrame에서 자신의 버튼을 관리 할 수있다. 이것은 많은 것을 단순화했습니다! 나는 그것을 충분히 말할 수 없다. 인터넷에는 너무 많은 기술과 의견이 포함되어 있고, 단순한 예가 충분하지 않다.
BentFX

1
@ user819640 "unbind"는 없으며 대신 std::bind(지정되지 않은) 개체를 반환하며 작업이 끝나면 범위를 벗어나게 할 수 있습니다. 바인딩 된 객체가 파괴되고 함수를 호출하려고하면 정의되지 않은 동작이 발생 합니다.
일부 프로그래머 친구

2
handler->addHandler(), 어딘가에 객체를 생성한다는 의미입니다 EventHandler. 좋은 대답 btw, +1.
gsamaras

2
인수 수와 일치하려면 자리 표시 자의 수가 필요하므로 콜백에 두 개의 인수가있는 경우 사용해야 ...., _1, _2)하는 식입니다.
Den-Jason

6

다음은 클래스 메서드 콜백 및 일반 함수 콜백과 함께 작동하는 간결한 버전입니다. 이 예에서 매개 변수가 처리되는 방법을 보여주기 위해 콜백 함수는 boolint.

class Caller {
  template<class T> void addCallback(T* const object, void(T::* const mf)(bool,int))
  {
    using namespace std::placeholders; 
    callbacks_.emplace_back(std::bind(mf, object, _1, _2));
  }
  void addCallback(void(* const fun)(bool,int)) 
  {
    callbacks_.emplace_back(fun);
  }
  void callCallbacks(bool firstval, int secondval) 
  {
    for (const auto& cb : callbacks_)
      cb(firstval, secondval);
  }
private:
  std::vector<std::function<void(bool,int)>> callbacks_;
}

class Callee {
  void MyFunction(bool,int);
}

//then, somewhere in Callee, to add the callback, given a pointer to Caller `ptr`

ptr->addCallback(this, &Callee::MyFunction);

//or to add a call back to a regular function
ptr->addCallback(&MyRegularFunction);

이는 C ++ 11 특정 코드를 Caller 클래스의 addCallback 메소드 및 개인 데이터로 제한합니다. 나에게 적어도 이것은 그것을 구현할 때 실수 할 가능성을 최소화합니다.


4

원하는 것은이 코드를 처리하는 인터페이스를 만들고 모든 클래스가 인터페이스를 구현하는 것입니다.

class IEventListener{
public:
   void OnEvent(int x) = 0;  // renamed Callback to OnEvent removed the instance, you can add it back if you want.
};


class MyClass :public IEventListener
{
    ...
    void OnEvent(int x); //typically such a function is NOT static. This wont work if it is static.
};

class YourClass :public IEventListener
{

이것이 작동하려면 "콜백"기능이 개선 되었다고 생각 하는 정적이 아닙니다 . 정적으로 만들려면 JaredC가 템플릿으로 제안한대로 수행해야합니다.


당신은 그것의 한쪽 만 보여주고 있습니다. 이벤트를 시작하는 방법을 보여줍니다.
Christopher Pisz

3

위 코드의 완전한 작업 예제 .... C ++ 11 :

#include <stdlib.h>
#include <stdio.h>
#include <functional>

#if __cplusplus <= 199711L
  #error This file needs at least a C++11 compliant compiler, try using:
  #error    $ g++ -std=c++11 ..
#endif

using namespace std;

class EventHandler {
    public:
        void addHandler(std::function<void(int)> callback) {
            printf("\nHandler added...");
            // Let's pretend an event just occured
            callback(1);
        }
};


class MyClass
{
    public:
        MyClass(int);
        // Note: No longer marked `static`, and only takes the actual argument
        void Callback(int x);

    private:
        EventHandler *pHandler;
        int private_x;
};

MyClass::MyClass(int value) {
    using namespace std::placeholders; // for `_1`

    pHandler = new EventHandler();
    private_x = value;
    pHandler->addHandler(std::bind(&MyClass::Callback, this, _1));
}

void MyClass::Callback(int x) {
    // No longer needs an explicit `instance` argument,
    // as `this` is set up properly
    printf("\nResult:%d\n\n", (x+private_x));
}

// Main method
int main(int argc, char const *argv[]) {

    printf("\nCompiler:%ld\n", __cplusplus);
    new MyClass(5);
    return 0;
}


// where $1 is your .cpp file name... this is the command used:
// g++ -std=c++11 -Wall -o $1 $1.cpp
// chmod 700 $1
// ./$1

출력은 다음과 같아야합니다.

Compiler:201103

Handler added...
Result:6

1

MyClass그리고 YourClass둘 다 SomeonesClass추상 (가상) Callback방법 을 가진 파생 될 수 있습니다 . 당신은 addHandler유형의 개체를 받아들이 SomeonesClassMyClassYourClass재정의 할 수 있습니다 Callback콜백 행동의 특정 구현을 제공 할 수 있습니다.


내가하는 일을 위해 나는이 아이디어를 가지고 놀았다. 그러나 내 핸들러를 사용하는 매우 다른 클래스의 수가 많기 때문에 옵션으로 보지 못했습니다.
BentFX

0

다른 매개 변수를 가진 콜백이있는 경우 다음과 같이 템플릿을 사용할 수 있습니다.
// 다음으로 컴파일 : g ++ -std = c ++ 11 myTemplatedCPPcallbacks.cpp -o myTemplatedCPPcallbacksApp

#include <functional>     // c++11

#include <iostream>        // due to: cout


using std::cout;
using std::endl;

class MyClass
{
    public:
        MyClass();
        static void Callback(MyClass* instance, int x);
    private:
        int private_x;
};

class OtherClass
{
    public:
        OtherClass();
        static void Callback(OtherClass* instance, std::string str);
    private:
        std::string private_str;
};

class EventHandler
{

    public:
        template<typename T, class T2>
        void addHandler(T* owner, T2 arg2)
        {
            cout << "\nHandler added..." << endl;
            //Let's pretend an event just occured
            owner->Callback(owner, arg2);
         }   

};

MyClass::MyClass()
{
    EventHandler* handler;
    private_x = 4;
    handler->addHandler(this, private_x);
}

OtherClass::OtherClass()
{
    EventHandler* handler;
    private_str = "moh ";
    handler->addHandler(this, private_str );
}

void MyClass::Callback(MyClass* instance, int x)
{
    cout << " MyClass::Callback(MyClass* instance, int x) ==> " 
         << 6 + x + instance->private_x << endl;
}

void OtherClass::Callback(OtherClass* instance, std::string private_str)
{
    cout << " OtherClass::Callback(OtherClass* instance, std::string private_str) ==> " 
         << " Hello " << instance->private_str << endl;
}

int main(int argc, char** argv)
{
    EventHandler* handler;
    handler = new EventHandler();
    MyClass* myClass = new MyClass();
    OtherClass* myOtherClass = new OtherClass();
}

1
OP의 문제를 해결하기 위해 무엇을했는지 설명해 주시겠습니까? OP의 완전한 코드를 포함하는 것이 정말로 필요합니까? OP는 그의 코드가 그의 YourClass. 해당 클래스를 제거하고 다른 OtherClass. 또한이 질문은 이미 좋은 답변을 받았습니다. 게시 할 가치가있는 솔루션이 얼마나 더 나은가요?
honk

내 게시가 더 나은 해결책이라고 말하지 않았습니다. 템플릿 방식으로 "OtherClass"를 사용하는 방법을 보여주었습니다.
mohDady 2014 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.