Search code examples
c++scheduled-tasksfunction-pointersmember

Handle non-member function pointer and member function pointer at the same time


I want to do a member function that will call every X seconds. I did a little prototype that can handle non member function, but I don't know if I did it well, and I can't handle both member function and non member function.

I have an Event object, which handle the function and the delay, with a basic timer, to detect when we need to run the function:

typedef void (*ScheduleFunction)(float dt);

class Event
{
private:
    ScheduleFunction m_Func;
    double m_Timer;
    double m_Delay;

public:
    Event(ScheduleFunction function, double delay)
    {
        m_Func = function;
        m_Delay = delay;
    }

    void Call(float dt)
    {
        m_Timer += dt;
        if (m_Timer >= m_Delay)
        {
            m_Func(dt);
            m_Timer = 0.0;
        }
    }
};

Then, I have another object that call every frames each function into a vector<Event>:

class Handler
{
private:
    void m_MemberFunction(float dt)
    {
        std::cout << "A member function." << std::endl;
    }

    std::vector<Event> m_ScheduleItems;

public:
    Handler()
    {
        // This will not compile, because the function expect a non member function
        Schedule(&Handler::m_MemberFunction, 1.0);
    }


    void CallScheduledFunctions(float dt)
    {
        for (std::vector<Event>::iterator it = m_ScheduleItems.begin(); it != m_ScheduleItems.end(); ++it)
        {
            it->Call(dt);
        }
    }



    void Schedule(ScheduleFunction func, double delay)
    {
        Event event(func, delay);
        m_ScheduleItems.push_back(event);
    }




    void Unschedule()
    {
        // TODO
    }

};

As you can see, I have a function Schedule that register new Event. But right now, it only handle non member function. Is there a way that I can handle non member function and member function, not only from the Handler but also on all others objects?

If there is no way, how can I achieve this?


Solution

  • UPDATE 1 Added "call any object's members" below.

    BRIEF

    I recommend using std::function and std::bind. But remind that std::function has some overhead due to the internal mechanisms!

    std::function is very powerful as there are many things you can store in it.

    Important: Using a function-pointer only approach is possible, but would cause some code and complexity if you must retain the simple unified interface.

    EXAMPLE

    #include <functional>
    
    using ScheduleFunction_t = std::function<void(float)>;
    
    class Event {
    private:
        ScheduleFunction_t
            m_Func;
        double
            m_Timer,
            m_Delay;
    
    public:
        Event(
            ScheduleFunction_t const&function, 
            double                   delay)
            : m_Func(function)
            , m_Delay(delay)
        { }
    
        void Call(float dt) {
            m_Timer += dt;
            if (m_Timer >= m_Delay)
            {
                // Important, if you do not assert in the constructor, check if the fn is valid...
                // The ctr shouldn't throw on runtime assert fail... memory leak and incpomplete construction...
                if(m_Func) 
                    m_Func(dt); 
    
                m_Timer = 0.0;
            }
        }
    };
    

    As you can see, including the <functional> header will give you the template std::function<R(Args...)>, where R is the return type and Args... a comma separated list of fully qualified argument types.

    void g_freeFunction(float f) {
        std::cout << "Globally floating for " << f << "ms" << std::endl;
    }
    
    class Handler {
    private:
        void m_MemberFunction(float dt) {
            std::cout << "Floating around with " << dt << " m/s" << std::endl;
        }
    
        std::vector<Event> m_ScheduleItems;
    
    public:
        Handler() {        
            // Bind member function
            Schedule<Handler, &Handler::m_MemberFunction>(this);
            // Or free
            Schedule(&g_freeFunction);
            // Or lambda
            Schedule([](float f) -> void { std::cout << "Weeeeeeeh...." << std::endl; });
        }
    
        void CallScheduledFunctions(float dt)
        {
            for(Event& e : m_ScheduleItems)
                e.Call(dt);        
        }
    
        template <typename TClass, void(TClass::*TFunc)(float)>
        void Schedule(
            TClass *const pInstance,
            double        delay = 0.0)
        {
            m_ScheduleItems.emplace_back(std::bind(TFunc, pInstance, std::placeholders::_1), delay); // Create in place at the end of vector.
        }
    
        void Schedule(
            ScheduleFunction_t fn,
            double             delay = 0.0) 
        {
            m_ScheduleItems.emplace_back(fn, delay); // Create in place at the end of vector.
        }
    
    
        void Unschedule() { /* TODO */ }
    };
    

    This way you can now bind almost whatever you want. :D

    Update: The Schedule-function can not be called for any other type that has a matching public method, e.g.:

    struct Test {
        void foo(float f) { 
            std::cout << "TEST ME!" << std::endl;
        }
    };   
    
    int main()
    {
        Test t={};
    
        Handler h = Handler();
        h.Schedule<Test, &Test::foo>(&t);
    
        for(uint32_t k=0; k < 32; ++k)
            h.CallScheduledFunctions(k);
    }
    

    RESOURCES

    http://en.cppreference.com/w/cpp/utility/functional http://en.cppreference.com/w/cpp/utility/functional/function http://en.cppreference.com/w/cpp/utility/functional/bind

    WORKING EXAMPLE

    http://cpp.sh/7uluut