Reputation: 21548
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
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