Step 7 of 7
Challenge: callbacks with std::function
Sometimes you want to store "something to call later": a button's click handler, a callback when a download finishes, a list of listeners. In C that's a function pointer plus a void * for extra data. C++ has a type-safe version: std::function<R(Args...)> (from <functional>) holds any callable with that signature: a plain function, a lambda (including one with captures), or an object with operator().
#include <functional>
#include <iostream>
#include <string>
#include <vector>
int twice(int x) { return 2 * x; }
int main() {
std::vector<std::function<int(int)>> steps;
int offset = 100;
steps.push_back(twice); // a plain function
steps.push_back([](int x) { return x + 1; }); // a lambda
steps.push_back([offset](int x) { return x + offset; }); // a lambda with a capture
int value = 5;
for (const auto& f : steps) value = f(value);
std::cout << value << "\n";
std::function<void(const std::string&)> on_done;
if (!on_done) std::cout << "no handler yet\n";
int calls = 0;
on_done = [&calls](const std::string& file) { calls++; std::cout << "got " << file << "\n"; };
on_done("a.zip");
on_done("b.zip");
std::cout << calls << " calls\n";
}
111
no handler yet
got a.zip
got b.zip
2 calls
How it works
- The type in the angle brackets is a function signature:
int(int)takes an int and returns an int;void(const std::string&)takes a string and returns nothing. - Different kinds of callables can live in the same vector, as long as they match the signature.
- An empty
std::functionconverts tofalse. Calling an empty one is an error, so check first when it might be unset.
Designing the event bus
Each subscription needs three things: its id, its event name, and its handler. A small struct in a std::vector keeps them in registration order, which is the order emit must use. Keep a counter member for the next id, starting at 1.
onappends a subscription and returns the new id.offfinds the id and erases it (returnfalseif it isn't there).emitloops over the subscriptions, calls the handlers whose event name matches, and counts them.
Your turn: build an EventBus where handlers subscribe to named events:
int on(const std::string& event, std::function<void(const std::string&)> fn)registers a handler and returns a unique id (1, 2, 3, ...)bool off(int id)unregisters; returns false if the id isn't registeredint emit(const std::string& event, const std::string& payload)calls every handler for that event, in registration order, and returns how many ran