Home > Software engineering >  Buffering callbacks that need this ptr?
Buffering callbacks that need this ptr?

Time:12-01

I'm just porting some code from javascript to C . As some might know, in JS it's pretty ususal to buffer callbacks in a vector, but how can I do the same in C ?

Consider the following - not yet working - code. What I'm doing here is for an object tree to register a callback with its leaf (sorry couldn't think of better names). As the callback function will access members within tree itself, it needs to capture the this-pointer. The problem that arises now is twofold:

  1. The leaf class layout needs to be known, therefore I need to provide the type of the callable to the vector. But it's impossible to know the type of a lambda beforehand, especially if it catches the this ptr.

  2. Even if the type could be deduced beforehand, I still have the problem that the lambda type would probably only allow this-pointers of a specific object to be embedded into its type, thus rendering every call to register_callback() that doesn't originate from tree as unavailable.

myleaf_; }; int main() { tree Tree1; Tree1.myleaf_.do_leave(); }'),l:'5',n:'0',o:'C++ source #1',t:'0')),k:47.31070496083551,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((g:!((h:compiler,i:(compiler:g122,deviceViewOpen:'1',filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'0',intel:'0',libraryCode:'0',trim:'1'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:c++,libs:!(),options:'-Wall -Os --std=c++20',selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:' x86-64 gcc 12.2 (Editor #1)',t:'0')),header:(),l:'4',m:41.3677130044843,n:'0',o:'',s:0,t:'0'),(g:!((h:output,i:(compilerName:'x86-64 gcc 12.2',editorid:1,fontScale:14,fontUsePx:'0',j:1,wrap:'1'),l:'5',n:'0',o:'Output of x86-64 gcc 12.2 (Compiler #1)',t:'0')),k:50,l:'4',m:58.632286995515706,n:'0',o:'',s:0,t:'0')),k:52.689295039164485,l:'3',n:'0',o:'',t:'0')),l:'2',n:'0',o:'',t:'0')),version:4" rel="nofollow noreferrer">CompilerExplorer

#include <vector>
#include <cstdio>

template <std::invocable Cb>
class leaf
{
public:
    auto register_callback(Cb)
    {

    }

    auto do_leave()
    {
        for (auto cb : callbacks_) {
            cb();
        }
    }

    std::vector<Cb> callbacks_;
};

class tree
{
public:
    tree()
    {
        myleaf_.register_callback([this](){
            do_some_messageing();
        });
    }

    auto do_some_messageing() -> void
    {
        printf("Hello World\n");
    }

    leaf<???> myleaf_;
    
};

int main()
{
    tree Tree1;

    Tree1.myleaf_.do_leave();
}

What would I have to do to circumvent those problems? If possible without std::function. I'm also open for different approaches.

CodePudding user response:

If you really want to avoid using std::function (I don't understand why, but it might improve debuggability?) you could simply implement a own function handler. Then you could make virtual functions do the heavy lifting.

If you dont like when the leaf handles pointers, you could hide the pointer magick inside some wrapper class.

If you dont want heap allocated object, you could have the memory allocated in your callback type and "allocate" memory on your reserved space.

std::function is easiest, but there is plenty of other alternatives.

#include <vector>
#include <cstdio>
#include <memory>

class callback_base {
    public:
    virtual void operator() () = 0;
    virtual ~callback_base() = default;
};

template <typename Cb>
struct callback: public callback_base {
    callback(Cb cb): _callback{cb} {}

    Cb _callback;

    void operator() () override {
        _callback();
    }
};

class leaf
{
public:
    template <typename Cb>
    auto register_callback(Cb cb)
    {
        callbacks_.push_back(std::make_unique<callback<Cb>>(cb));
    }

    auto do_leave()
    {
        for (auto &cb : callbacks_) {
            (*cb)();
        }
    }

    std::vector<std::unique_ptr<callback_base>> callbacks_;
};

class tree
{
public:
    tree()
    {
        myleaf_.register_callback([this](){
            do_some_messageing();
        });
    }

    auto do_some_messageing() -> void
    {
        printf("Hello World\n");
    }

    leaf myleaf_;
    
};

int main()
{
    tree Tree1;

    Tree1.myleaf_.do_leave();
}

CodePudding user response:

As you figured, templates in Cpp require everything to be deducible at compile time because they effectively work similar to plain replacements. The compiled code contains definitions of all actually usable variants of the template. As a result of that, there will be separate definitions of the invocables and you cannot mix them into the same tree. "Abstracting" this entirely with wrapped function pointers - aka std::function is likely the best solution. You can wrap those further if you do not want to expose such a "free for all" input to the user of your class.

  • Related