Baruch
Baruch

Reputation: 21548

how to implement callback system

I am trying to implement a class system that uses callbacks to notify of certain events. I can think of 2 ways to do this:

#include <functional>
#include <iostream>

class A {
public:
    A(std::function<void(int)>&& func) : f(std::move(func)) {}
    void run() { f(2); }
    std::function<void(int)> f;
};

template <class F>
class B {
public:
    B(F&& func) : f(std::move(func)) {}
    void run() { f(2); }
    F f;
};

int main() {
    int n = 1;
    A a1([n](int b){std::cout << n+b << '\n';});
    B b1([n](int b){std::cout << n+b << '\n';});
    a1.run();
    b1.run();
}

What are the advantages/disadvantages of using these 2 approaches (having a template type for the callback type vs. using std::function)

Upvotes: 1

Views: 111

Answers (1)

Quimby
Quimby

Reputation: 19223

I think it boils down to whether you can afford to have templated B because F can spread through your code quite quickly. Consider:

struct Foo{
    B<???> member;
};

If B is used mostly locally, F is fixed, or F should be exposed through Foo, this second approach can do everything that A can and doesn't have the overhead of std::function. (Measure!)

If you cannot afford that, you will need some form of type erasure, a.k.a std::function. But std::variant could be handy too.

I mean if you go with std::function, which has fixed signature, abstract callable Event class with virtual void operator()(int) is a viable option too. Well, without the lambdas, or with LambdaEvent using std::function underneath. Still this event hiearchy might provide more "type safety".

Please note that as written, B can accept any callable, not just void(int). Consider using std::invoke(f,2) instead of f(2).

Upvotes: 2

Related Questions