Object-Oriented Callbacks for C++?

生来就可爱ヽ(ⅴ<●) 提交于 2019-11-29 02:13:37

The most OO way to use Callbacks in C++ is to call a function of an interface and then pass an implementation of that interface.

#include <iostream>

class Interface
{
  public:
  virtual void callback() = 0;
};

class Impl : public Interface
{
  public:
  virtual void callback() { std::cout << "Hi from Impl\n"; }
};

class User
{
  public:
  User(Interface& newCallback) : myCallback(newCallback) { }

  void DoSomething() { myCallback.callback(); }

  private:
  Interface& myCallback;
};

int main()
{
  Impl cb;
  User user(cb);
  user.DoSomething();
}

People typically use one of several patterns:

Inheritance. That is, you define an abstract class which contains the callback. Then you take a pointer/reference to it. That means that anyone can inherit and provide this callback.

class Foo {
    virtual void MyCallback(...) = 0;
    virtual ~Foo();
};
class Base {
    std::auto_ptr<Foo> ptr;
    void something(...) {
        ptr->MyCallback(...);
    }
    Base& SetCallback(Foo* newfoo) { ptr = newfoo; return *this; }
    Foo* GetCallback() { return ptr; }
};

Inheritance again. That is, your root class is abstract, and the user inherits from it and defines the callbacks, rather than having a concrete class and dedicated callback objects.

class Foo {
    virtual void MyCallback(...) = 0;
    ...
};
class RealFoo : Foo {
    virtual void MyCallback(...) { ... }
};

Even more inheritance- static. This way, you can use templates to change the behaviour of an object. It's similar to the second option but works at compile time instead of at run time, which can yield various benefits and downsides, depending on the context.

template<typename T> class Foo {
    void MyCallback(...) {
        T::MyCallback(...);
    }
};
class RealFoo : Foo<RealFoo> {
    void MyCallback(...) {
        ...
    }
};

You can take and use member function pointers or regular function pointers

class Foo {
    void (*callback)(...);
    void something(...) { callback(...); }
    Foo& SetCallback( void(*newcallback)(...) ) { callback = newcallback; return *this; }
    void (*)(...) GetCallback() { return callback; }
};

There are function objects- they overload operator(). You will want to use or write a functional wrapper- currently provided in std::/boost:: function, but I'll also demonstrate a simple one here. It's similar to the first concept, but hides the implementation and accepts a vast array of other solutions. I personally normally use this as my callback method of choice.

class Foo {
    virtual ... Call(...) = 0;
    virtual ~Foo();
};
class Base {
    std::auto_ptr<Foo> callback;
    template<typename T> Base& SetCallback(T t) {
        struct NewFoo : Foo {
             T t;
             NewFoo(T newt) : t(newt) {}
             ... Call(...) { return t(...); }
        };
        callback = new NewFoo<T>(t);
        return this;
    }
    Foo* GetCallback() { return callback; }
    void dosomething() { callback->Call(...); }
};

The right solution mainly depends on the context. If you need to expose a C-style API then function pointers is the only way to go (remember void* for user arguments). If you need to vary at runtime (for example, exposing code in a precompiled library) then static inheritance can't be used here.

Just a quick note: I hand whipped up that code, so it won't be perfect (like access modifiers for functions, etc) and may have a couple of bugs in. It's an example.

C++ allows function pointers on member objects.
See here for more details.
You can also use boost.signals or boost.signals2 (depanding if your program is multithreaded or not).

There are various libraries that let you do that. Check out boost::function.

Or try your own simple implementation:

template <typename ClassType, typename Result>
class Functor
{
 typedef typename Result (ClassType::*FunctionType)();
 ClassType* obj;
 FunctionType fn;
public:
 Functor(ClassType& object, FunctionType method): obj(&object), fn(method) {}

 Result Invoke()
 {
  return (*obj.*fn)();
 }

 Result operator()()
 {
  return Invoke();
 }
};

Usage:

class A
{
 int value;
public:
 A(int v): value(v) {}

 int getValue() { return value; }
};


int main()
{
 A a(2);

 Functor<A, int> fn(a, &A::getValue);

 cout << fn();
}

Joining the idea of functors - use std::tr1::function and boost::bind to build the arguments into it before registering it.

There are many possibilities in C++, the issue generally being one of syntax.

  • You can use pointer to functions when you don't require state, but the syntax is really horrid. This can be combined with boost::bind for an even more... interesting... syntax (*)
  • I correct your false assumption, it is indeed feasible to have pointer to a member function, the syntax is just so awkward you'll run away (*)
  • You can use Functor objects, basically a Functor is an object which overloads the () operator, for example void Functor::operator()(int a) const;, because it's an object it has state and may derive from a common interface
  • You can simply create your own hierarchy, with a nicer name for the callback function if you don't want to go the operator overloading road
  • Finally, you can take advantage of C++0x facilities: std::function + the lambda functions are truly awesome when it comes to expressiveness.

I would appreciate a review on lambda syntax ;)

Foo foo;
std::function<void(std::string const&,int)> func =
  [&foo](std::string const& s, int i) {
    return foo.say(s,"Hi from Foo",i);
  };

func("Hi from Bar", 2);
func("Hi from FooBar", 3);

Of course, func is only viable while foo is viable (scope issue), you could copy foo using [=foo] to indicate pass by value instead of pass by reference.

(*) Mandatory Tutorial on Function Pointers

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!