Home > OS >  C List of member callback functions
C List of member callback functions

Time:10-12

I am going from C development to C on the STM32 platform and simply cant find a suitable solution for my problem.

Please have a look at the simplified example code attached to this post.

#include <iostream>
#include <functional>
#include <list>

using namespace std;

class Pipeline {
public:
    std::list<std::function<void(Pipeline*)>> handlers;
    
    //add handler to list --> works fine
    void addHandler(std::function<void(Pipeline*)> handler) {
        this->handlers.push_front(handler);
    }
    
    void ethernetCallback(void) {
        //handle received data and notify all callback subscriptions --> still works fine
        // this callback function is normally sitting in a child class of Pipeline
        int len = handlers.size();
        for (auto const &handler : this->handlers) {
            handler(this);
        }
    }
    
    void removeHandler(std::function<void(Pipeline*)> handler) {
        // Here starts the problem. I can not use handlers.remove(handler) here to
        // unregister the callback function. I understood why I can't do that,
        // but I don't know another way of coding the given situation.
    }
};

class Engine {
public: 
    void callback(Pipeline *p) {
        // Gets called when new data arrives 
        cout<<"I've been called.";
    }
    void assignPipelineToEngine(Pipeline *p) {
        p->addHandler(std::bind(&Engine::callback, this, std::placeholders::_1));
    }
};

int main()
{
    Engine *e = new Engine();
    Pipeline *p = new Pipeline();
    e->assignPipelineToEngine(p);
    // the ethernet callback function would be called by LWIP if new udp data is available
    // calling from here for demo purposes only
    p->ethernetCallback();

    return 0;
}

The idea is that when the class "Pipeline" receives new data over ethernet, it informs all registered callback functions by calling a method. The callback functions are stored in a std::list. Everything works fine till here, but the problem with this approach is that I can't remove the callback functions from the list, which is required for the project. I know why I can't simply remove the callback function pointers from the list, but I don't know another approach at the moment.

Probably anybody could give me a hint where I could have a look for solving this problem. All resources I've researched don't really show my specific case.

Thank you all in advance for your support! :)

CodePudding user response:

One option would be to have addHandler return some sort of identifier that can later be passed to removeHandler. For example:

class Pipeline {
public:
    std::map<int, std::function<void(Pipeline*)>> handlers;
    int nextId = 0;
    
    //add handler to list --> works fine
    void addHandler(std::function<void(Pipeline*)> handler) {
        handlers[nextId  ] = handler;
    }
    
    void ethernetCallback(void) {
        for (auto const& entry : handlers) {
            entry.second(this);
        }
    }
    
    void removeHandler(int handlerToken) {
        handlers.erase(handlerToken);
    }
};

class Engine {
public:
    void callback(Pipeline *p) {
        // Gets called when new data arrives 
        cout<<"I've been called.";
    }

    void assignPipelineToEngine(Pipeline *p) {
        handlerToken = p->addHandler(
            std::bind(
                &Engine::callback,
                this,
                std::placeholders::_1
            )
        );
    }

    void unregisterPipelineFromEngine(Pipeline *p) {
        p->removeHandler(handlerToken);
    }
private:
    int handlerToken;
};

CodePudding user response:

Perhaps you could attach an ID to each handler. Very crude variant would just use this address as an ID if you have at most one callback per instance.

#include <functional>
#include <iostream>
#include <list>

using namespace std;

class Pipeline {
   public:
    using ID_t = void *;  // Or use integer-based one...
    struct Handler {
        std::function<void(Pipeline *)> callback;
        ID_t id;
        // Not necessary for emplace_front since C  20 due to agreggate ctor
        // being considered.
        Handler(std::function<void(Pipeline *)> callback, ID_t id)
            : callback(std::move(callback)), id(id) {}
    };
    std::list<Handler> handlers;

    // add handler to list --> works fine
    void addHandler(std::function<void(Pipeline *)> handler, ID_t id) {
        this->handlers.emplace_front(std::move(handler), id);
    }

    void ethernetCallback(void) {
        // handle received data and notify all callback subscriptions --> still
        // works fine
        //  this callback function is normally sitting in a child class of
        //  Pipeline
        int len = handlers.size();
        for (auto const &handler : this->handlers) {
            handler.callback(this);
        }
    }

    void removeHandler(ID_t id) {
        handlers.remove_if([id = id](const Handler &h) { return h.id == id; });
    }
};

class Engine {
   public:
    void callback(Pipeline *p) {
        // Gets called when new data arrives
        cout << "I've been called.";
    }
    void assignPipelineToEngine(Pipeline *p) {
        //p->addHandler(std::bind(&Engine::callback, this, std::placeholders::_1), this);
        //Or with a lambda
        p->addHandler([this](Pipeline*p){this->callback(p);},this);
    }
    void removePipelineFromEngine(Pipeline *p) { p->removeHandler(this); }
};

int main() {
    Engine *e = new Engine();
    Pipeline *p = new Pipeline();
    e->assignPipelineToEngine(p);
    // the ethernet callback function would be called by LWIP if new udp data is
    // available calling from here for demo purposes only
    p->ethernetCallback();

    return 0;
}

You might also consider std::map<ID_t,std::function<...>> instead of list, not sure how memory/performance constrained you are.

Obligatory: do not use new, use std::unique_ptr, or better use automatic storage whenever you can. Although in this case a pointer is appropriate for e as you need stable address due to this capture/bind/ID.

std::functions are not comparable as there isn't a good generic way how to define this comparison.

  • Related