DMarlon Studio

Lección 18 · 18 de 20

std::function y callbacks · C++

Construya un sistema de eventos en C++ con std::function: registre lambdas, funciones y métodos como callbacks y llámelos al emitir cada evento.

C++ · std::function y callbacks en DMarlon Studio: el código del ejemplo y su salida en la terminal

Un callback es una función que se entrega a otra parte del programa para que la llame cuando ocurra algo. Así funcionan los botones de una interfaz: en las aplicaciones de escritorio con FLTK que genera DMarlon Studio, cada pulsación llama a la función que se le ha asociado.

`std::function<void(const std::string&)>` puede guardar cualquier cosa que se pueda llamar con esa firma: una lambda, una función normal o un objeto con operator(). using Handler = ... le da un nombre, y handlers_ relaciona cada nombre de evento con su lista de callbacks.

on() registra un callback para un evento y emit() llama, en orden, a todos los de ese evento. Para click hay dos: una lambda que cuenta los clics capturando clicks por referencia y la función libre writeLog, que encaja porque tiene la misma firma.

Para llamar a un método de un objeto se usa una lambda que lo captura: [&cart](const std::string& p) { cart.add(p); }. El carrito suma un artículo con cada add. Al emitir close, que no tiene callbacks, el bucle no encuentra nada y no ocurre nada más.

Cuidado con las capturas por referencia: el bus guarda las lambdas, así que cart y clicks tienen que seguir vivos mientras se puedan emitir eventos. Aquí todo vive en main y no hay problema.

Código · main.cpp

#include <functional>#include <iostream>#include <map>#include <string>#include <vector>// A small event system: each event name keeps a list of callbacks.class EventBus {    using Handler = std::function<void(const std::string&)>;    // any callable with this signature    std::map<std::string, std::vector<Handler>> handlers_;public:    void on(const std::string& event, Handler h) { handlers_[event].push_back(h); }    void emit(const std::string& event, const std::string& data) {        std::cout << "emit " << event << "\n";        for (const Handler& h : handlers_[event]) h(data);    }};void writeLog(const std::string& data) { std::cout << "  [log] " << data << "\n"; }struct Cart {    int items = 0;    void add(const std::string& p) { std::cout << "  [cart] " << p << ", " << ++items << " item(s)\n"; }};int main() {    EventBus bus; Cart cart; int clicks = 0;    bus.on("click", [&clicks](const std::string& button) { std::cout << "  [ui] click " << ++clicks << ": " << button << "\n"; });    bus.on("click", writeLog);                                  // a plain function fits too    bus.on("add", [&cart](const std::string& p) { cart.add(p); });   // calls a method    bus.emit("click", "Buy now");    bus.emit("add", "Headphones");    bus.emit("add", "Charger");    bus.emit("close", "nobody listens");                        // no handlers: nothing runs}

Salida · c++ -std=c++17 main.cpp -o main && ./main

emit click
  [ui] click 1: Buy now
  [log] Buy now
emit add
  [cart] Headphones, 1 item(s)
emit add
  [cart] Charger, 2 item(s)
emit close

Para recordar

  • Un callback es una función que otro código llamará más tarde.
  • std::function<R(Args...)> guarda cualquier invocable con esa firma.
  • Lambdas, funciones libres y objetos con operator() valen igual.
  • Para un método, capture el objeto en una lambda.
  • Lo que captura por referencia debe vivir más que el callback.

Ejercicio

Añada un método off(event) que borre todos los callbacks de un evento. Llame a bus.off("click") y emita otro clic para comprobar que ya no se escribe nada.