0

Inspired by another article here on SO (C++ callback using class member) I tried to write a universal CallbackHandler.

CallbackHandler.hpp

#pragma once
#include <functional>

template <typename CallbackClass, typename CallbackArgType>
class CallbackHandler
{
public:
    std::function<void(CallbackArgType ct)> m_callbackFunc;
    CallbackHandler(CallbackClass * handler, std::function<void(CallbackArgType)> method)
    {
        //m_callbackFunc is supposed to stand for a member to pointer callback function with one
        //parameter of any type
        m_callbackFunc = std::bind(method, handler, std::placeholders::_1);
    }
};
#include "wrapper_T.cpp"

I want to use it in several other templated namespaces/classes like here:

wrapper.hpp

//this wrappers main purpose is to combine the constructor of a non templated class (MyModule) 
//and hold a (global) callback method for it (m_parentCallback)
namespace wrapper
{
  extern std::function<void(wxImage *)> m_parentCallback;

  template<typename ParentClass>
  MyModule GetNewModule(ParentClass* parent, void (ParentClass::* method)(wxImage *));
}

wrapper.cpp

namespace wrapper
{
  //This is only to avoid multiple definition error - actual definition is in wrapper_T.cpp 
  std::function<void(wxImage *)> m_parentCallback;
}

wrapper_T.cpp

namespace wrapper
{
  template<typename ParentClass>
  MyModule GetNewModule(ParentClass* parent, void (ParentClass::* method)(wxImage *))
  {
    //the callback type of this wrapper/class is wxImage*
    std::shared_ptr<CallbackHandler<ParentClass, wxImage*>> handler = 
      std::make_shared< CallbackHandler<ParentClass, wxImage*>>(parent, method);
//EDIT - SOLVED: <- Error C2664: Cant convert argument 2 from "void (__thiscall MyModule::*)(void)" to "std::function<void(wxImage*)>"

    m_parentCallback = std::bind(&CallbackHandler<ParentClass, wxImage*>::m_callbackFunc, handler, std::placeholders::_1);
//<- Error C2679: no suitable binary operator "=" found

    return std::make_unique<MyModule>();
  }
}

I wanted to use the callback like this:

MyModule.cpp

  wrapper::m_parentCallback(&img);

I want to initialize the whole thing like this:

MainClass.cpp

  MainClass::MainClass()
  {
    //declared in header: std::unique_ptr<MyModule> module
    module = std::move(wrapper::GetNewModule(this, &MainClass::CallbackFunc));
  }

  void MainClass::CallbackFunc(wxImage * img)
  { /* do something with it */ }

I have the class with the "this" pointer and the pointer to method "CallbackFunc", which should be alright. But I dont see how to use my CallbackHandler class for a std::function callback pointer.

Or did I overdo it with the wrapper holding a pointer to a method of the CallbackHandler, which holds a pointer to a method of the actual callback method?

All of this is no design choice, I just want the CallbackHandler to be portable and working, while having an interface which is easy to use.


EDIT: I tried to apply the comments suggestions on the code, but I was to fast with claiming the first problem was solved. The error was just hidden by the next error. If I try to compile with just this line:

std::shared_ptr<CallbackHandler<ParentClass, wxImage*>> handler =
  std::make_shared< CallbackHandler<ParentClass, wxImage*>>(parent, method);
//<- Error C2664: "CallbackHandler<ParentClass,wxImage *>::
//CallbackHandler(CallbackHandler<ParentClass,wxImage *> &&)" 
//: converting argument 2 from "void (__thiscall MainClass::* )(wxImage *)" 
//to "std::function<void (wxImage *)>" not possible
//        with
//        [
//            ParentClass=MainClass
        ] 
//(freely translated into english by me)

So, the missing argument was not the only problem. If std::bind on methods (member functions) does not work, I do have to change the CallbackClass as well, dont I? Maybe something along those lines:

std::function<void(CallbackArgType cat)> m_callbackFunc;
CallbackHandler(CallbackClass * handler, std::function<void(CallbackArgType)> method)
{
    //m_callbackFunc = std::bind(method, handler, std::placeholders::_1);
    m_callbackFunc = [method](auto img) { method(img); };
}

Natulux
  • 187
  • 1
  • 1
  • 11
  • Side note: The `std::move` is unnecessary. You only need it to turn named things into temporaries (roughly speaking). Function return values are by definition temporary and will be moved from automatically. – Max Langhof Oct 24 '19 at 16:27
  • Could you correct or verify that you really want to have `MyModule GetNewModule(...);` in the header but `Screenshotmodul GetNewScreenshotmodul(...);` in the .cpp? – Max Langhof Oct 24 '19 at 16:29
  • The first error says that you want to pass a function pointer that takes no argument to a function which expects a `std::function` that does take an argument. Gotta agree with the compiler: `method` takes no argument so you can't use it as a `std::function` that takes a `CallbackArgType`. The subsequent `std::bind` failure might be the consequence of this earlier error. – Max Langhof Oct 24 '19 at 16:32
  • In any case, I can't shake the feeling that you're deeply overcomplicating something here... – Max Langhof Oct 24 '19 at 16:33
  • Oh, I see now. You're trying to use a member `std::function` as if it was a member function. That won't work. But it's a good reason to use a lambda instead. – Max Langhof Oct 25 '19 at 07:20

2 Answers2

0

Replace

m_parentCallback = std::bind(&CallbackHandler<ParentClass, wxImage*>::m_callbackFunc, handler, std::placeholders::_1);

with

m_parentCallback = [handler](auto img){ handler->m_parentCallback(img); };

I don't think bind was designed to work with member functor objects on top of everything. But lambdas handle that seamlessly.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • This seems like a reasonable approach and I applied it. I can't see the results of this yet, because of the previous error (I edited the question) – Natulux Oct 25 '19 at 07:54
0

I noticed that my problem is beeing solved by every event handler, calling an event function. So I used an event handler as basis vor my CallbackHandler. I found and used this event handler, as there are most certainly many other good written examples: https://www.codeproject.com/Articles/1256352/CppEvent-How-to-Implement-Events-using-Standard-Cp#holdingHandlerFunction

I just wrapped it in a class and it is a little overkill for my purpose, but it works. All credits to Shmuel Zang (see link above)

EventHandler.hpp

#ifndef EventHandler_hpp
#define EventHandler_hpp

// https://www.codeproject.com/Articles/1256352/CppEvent-How-to-Implement-Events-using-Standard-Cp#holdingHandlerFunction

#include <functional>
#include <list>
#include <algorithm>
#include <utility>
#include <atomic>
#include <mutex>
#include <future>

namespace EventHandler
{
    template <typename... Args> class EventHandler_Base
    {
    public:
        typedef std::function<void(Args...)> handler_func_type;
        typedef unsigned int handler_id_type;

        explicit EventHandler_Base(const handler_func_type& handlerFunc)
            : m_handlerFunc(handlerFunc)
        {
            m_handlerId = ++m_handlerIdCounter;
        }

        // copy constructor
        EventHandler_Base(const EventHandler_Base& src)
            : m_handlerFunc(src.m_handlerFunc), m_handlerId(src.m_handlerId)
        {
        }

        // move constructor
        EventHandler_Base(EventHandler_Base&& src)
            : m_handlerFunc(std::move(src.m_handlerFunc)), m_handlerId(src.m_handlerId)
        {
        }

        // copy assignment operator
        EventHandler_Base& operator=(const EventHandler_Base& src)
        {
            m_handlerFunc = src.m_handlerFunc;
            m_handlerId = src.m_handlerId;

            return *this;
        }

        // move assignment operator
        EventHandler_Base& operator=(EventHandler_Base&& src)
        {
            std::swap(m_handlerFunc, src.m_handlerFunc);
            m_handlerId = src.m_handlerId;

            return *this;
        }

        // function call operator
        void operator()(Args... params) const
        {
            if (m_handlerFunc)
            {
                m_handlerFunc(params...);
            }
        }

        bool operator==(const EventHandler_Base& other) const
        {
            return m_handlerId == other.m_handlerId;
        }

        operator bool() const
        {
            return m_handlerFunc;
        }

        handler_id_type id() const
        {
            return m_handlerId;
        }

    private:
        handler_func_type m_handlerFunc;
        handler_id_type m_handlerId;
        static std::atomic_uint m_handlerIdCounter;
    };

    template <typename... Args> std::atomic_uint EventHandler_Base<Args...>::m_handlerIdCounter(0);

    template <typename... Args> class Event_Base
    {
    public:
        typedef EventHandler_Base<Args...> handler_type;

        Event_Base()
        {
        }

        // copy constructor
        Event_Base(const Event_Base& src)
        {
            std::lock_guard<std::mutex> lock(src.m_handlersLocker);

            m_handlers = src.m_handlers;
        }

        // move constructor
        Event_Base(Event_Base&& src)
        {
            std::lock_guard<std::mutex> lock(src.m_handlersLocker);

            m_handlers = std::move(src.m_handlers);
        }

        // copy assignment operator
        Event_Base& operator=(const Event_Base& src)
        {
            std::lock_guard<std::mutex> lock(m_handlersLocker);
            std::lock_guard<std::mutex> lock2(src.m_handlersLocker);

            m_handlers = src.m_handlers;

            return *this;
        }

        // move assignment operator
        Event_Base& operator=(Event_Base&& src)
        {
            std::lock_guard<std::mutex> lock(m_handlersLocker);
            std::lock_guard<std::mutex> lock2(src.m_handlersLocker);

            std::swap(m_handlers, src.m_handlers);

            return *this;
        }

        typename handler_type::handler_id_type add(const handler_type& handler)
        {
            std::lock_guard<std::mutex> lock(m_handlersLocker);

            m_handlers.push_back(handler);

            return handler.id();
        }

        inline typename handler_type::handler_id_type add(const typename handler_type::handler_func_type& handler)
        {
            return add(handler_type(handler));
        }

        bool remove(const handler_type& handler)
        {
            std::lock_guard<std::mutex> lock(m_handlersLocker);

            auto it = std::find(m_handlers.begin(), m_handlers.end(), handler);
            if (it != m_handlers.end())
            {
                m_handlers.erase(it);
                return true;
            }

            return false;
        }

        bool remove_id(const typename handler_type::handler_id_type& handlerId)
        {
            std::lock_guard<std::mutex> lock(m_handlersLocker);

            auto it = std::find_if(m_handlers.begin(), m_handlers.end(),
                [handlerId](const handler_type& handler) { return handler.id() == handlerId; });
            if (it != m_handlers.end())
            {
                m_handlers.erase(it);
                return true;
            }

            return false;
        }

        void call(Args... params) const
        {
            handler_collection_type handlersCopy = get_handlers_copy();

            call_impl(handlersCopy, params...);
        }

        std::future<void> call_async(Args... params) const
        {
            return std::async(std::launch::async, [this](Args... asyncParams) { call(asyncParams...); }, params...);
        }

        inline void operator()(Args... params) const
        {
            call(params...);
        }

        inline typename handler_type::handler_id_type operator+=(const handler_type& handler)
        {
            return add(handler);
        }

        inline typename handler_type::handler_id_type operator+=(const typename handler_type::handler_func_type& handler)
        {
            return add(handler);
        }

        inline bool operator-=(const handler_type& handler)
        {
            return remove(handler);
        }

    protected:
        typedef std::list<handler_type> handler_collection_type;

        void call_impl(const handler_collection_type& handlers, Args... params) const
        {
            for (const auto& handler : handlers)
            {
                handler(params...);
            }
        }

        handler_collection_type get_handlers_copy() const
        {
            std::lock_guard<std::mutex> lock(m_handlersLocker);

            // Since the function return value is by copy, 
            // before the function returns (and destruct the lock_guard object),
            // it creates a copy of the m_handlers container.

            return m_handlers;
        }

    private:
        handler_collection_type m_handlers;
        mutable std::mutex m_handlersLocker;
    };

}

#endif // EventHandler_hpp

I then use this generic class to save a callback member method:

CallbackHandler.hpp

#ifndef CallbackHandler_hpp
#define CallbackHandler_hpp

#pragma once

#include "EventHandler.hpp"

#include <functional>
#include <mutex>

using namespace EventHandler;

template <typename... Args>
class CallbackHandler
{
public:
    typedef std::function<void(Args...)> callbackFunc_type;

    explicit CallbackHandler() : mb_callbackIsSet(false), mi_handlerID(0){}
    explicit CallbackHandler(const callbackFunc_type& handlerFunc) : mb_callbackIsSet(false), mi_handlerID(0)
    {
        SetCallbackMethod(handlerFunc);
    }

    ~CallbackHandler()
    {
        m_callbackEvent.remove_id(mi_handlerID);
    }

    void SetCallbackMethod(const callbackFunc_type& handlerFunc)
    {
        m_callbackEvent.remove_id(mi_handlerID);
        mi_handlerID = m_callbackEvent.add([=](Args... params) {

            handlerFunc(params...);
        });
        mb_callbackIsSet = true;
    }

    bool DoCallback(Args... params)
    {
        if (mb_callbackIsSet)
        {
            std::lock_guard<std::mutex> lock(callbackLocker);
            m_callbackEvent(params...);
            return true;
        }

        return false;
    }

private:
    unsigned int mi_handlerID;
    Event_Base<Args...> m_callbackEvent;
    bool mb_callbackIsSet;
    std::mutex callbackLocker;
};

#endif //CallbackHandler_hpp

I can use the CallbackHandler in every other class now:

//Imagine an example class named Screenshotmodul
//In this example I use wxImage as a callback object, it is the output of my Screenshotmodul class and should be given back to my main class
//It could be any other (or several others like this: <ObjectType, OtherCallbackType> )

class Screenshotmodul
{
public:
//[...]

    CallbackHandler<wxImage> m_callbackHandler;

    template<typename ParentClass>
    void SetCallbackMethod(ParentClass* parent, void (ParentClass::* method)(wxImage))
    {
        //using the given object pointer and the given member method in a lambda function
        //saving that lambda as callback method
        m_callbackHandler.SetCallbackMethod([=](wxImage img) {
            (parent->*method)(img); });
    }
}

In my main class I can then set a callback method:

std::unique_ptr<Screenshotmodul> sm = std::make_unique<Screenshotmodul>();
sm->SetCallbackMethod(this, &MyMainClass::CallbackfuncForScreenshotmodul);

//Of course there should be a callback function as just described:
void MyMainClass::CallbackfuncForScreenshotmodul(wxImage img)
{
  //img now contains the callback value, that the Screenshotmodul class created
}

This is my approach and it is not perfect, but it works for me.

Cheers Natu

Natulux
  • 187
  • 1
  • 1
  • 11