Search code examples
c++pointersarduinofunction-pointersarduino-c++

Pointer to function from another pointer


I'm new to c++ and I'm trying to make a generic switch (i.e. the device, not the C++ statement) that could be used to blink lights, turn beeps on and off, etc, in my Arduino project.

I could create a switchable interface and implement that in the classes that I want to "switch". But since I'm doing it as study purposes and I saw the pointer-to-functions ability in C++ (that is new to me since I come from C# and Java), I tough it would be a good opportunity to give it a try...

The problem is that I can pass the function in my code only if it's a local function but it won't work if I try to pass a function from another object like a led for example.

Some code to illustrate the problem. This is the switch.cpp, it recieves the On and Off functions in it's constructor and it has a update method that is called inside the loop method in the Arduino ino main class:

auto_switch.cpp

using switch_function = void(*)();
auto_switch::auto_switch(const switch_function on_function, const switch_function off_function, const int max_speed_count)
{
    //sets all variables...
}

void auto_switch::update(const unsigned long millis)
{
    //turn switch on and off...
}

And this is my ino file

ino file

#include <Arduino.h>
#include "led.h"
#include "auto_switch.h"

led* main_led;
auto_switch* led_switch;
int slow_speed;

//ugly code
void turn_led_on()
{
    main_led->turn_on();
}

//ugly code
void turn_led_off()
{
    main_led->turn_off();
}

void setup() {
    main_led = new led(2, 3, 4, true, color::white);

    //ugly code
    led_switch = new auto_switch(turn_led_on, turn_led_off, 3);

    slow_speed = led_switch->add_speed(100, 100, 3, 1000);
    led_switch->set_active_speed(slow_speed);
    led_switch->turn_on();
}

void loop() {
    led_switch->update(millis());
}

It works but I had to make a local function (turn_led_on and turn_led_off) to be able to assign the inner functions as a parameter to the auto_switch constructor, the parts that I've wrote //ugly code

I wanted to do something like this, without the glue code in between:

//doesn't work
led_switch = new auto_switch(main_led->turn_on, main_led->turn_off, 3);

Is it possible? I've read something about static pointer to function and some std functions that help with that, if I get it right the glue code is necessary in this case so that the compiler can know where the functions are coming from I guess (from which object), but since the functions I need to call cannot be static I've discarded this option, and the std functions I believe it can't be used with the Arduino or could but shouldn't for performance limitations...

Anyway, does it make sense, can it be done using pointer to functions or should I create a interface or something different?


Solution

  • Before deciding how to do it, the qquestion is what do you want to do and why. Because, maybe there are better alternatives using simple C++ idioms.

    Option 1: specialization with polymorphism

    Do you want to specialize some functions of your switch, so instead of calling the function of the auto_switch you'd call dome more specialized ones ?

    In this case you wouldn't do:

    //doesn't work
    led_switch = new auto_switch(main_led->turn_on, main_led->turn_off, 3);
    

    but instead you would rely on polymorphism with virtual functions in the base class:

    class auto_switch {
    ...
        virtual void turn_on();  
        virtual void turn_off(); 
    ...
    }; 
    

    and write a specialized class for the leds:

    class led_witch : public auto_switch {
    ...
        void turn_on() override;  
        void turn_off() override; 
    ...
    }; 
    

    In fact, the compiler will generate some function pointers behind the scene, but you don't have to care:

    auto_switch s1=new auto_switch(...); 
    auto_switch s2=new led_switch(...);   // no problem !! 
    
    s1->turn_on();     // calls auto_switch::turn_on() 
    s2->turn_on();     // calls led_switch::turn_on() since the real type of s2 is led_switch 
    

    But event if each object's behavior is dynamic on the the base of the real class of the object, the objects of the same class share a behavior that was predefined at compile time. If this is not ok, go to the next option.

    Option 2: the member function pointer

    The functions of another objects can only be invoked with that object at hand. So having a function pointer to a led function is not sufficient: you also need to know on which led it shall be applied.

    This is why member function pointers are different and somewhat constraint: you can only invoke functions of class of your member function pointer. If polymorphism is sufficient (i.e. if derived class has a different implementation of a function already foreseen in the base classe) then you are lucky. If you want to use a function that only exists in the derived class and not in the base class, it won't compile.

    Here a simplified version of auto_swith: I provide a function, but allso a pointer to the object on which the function has to be invoked:

    class auto_switch{
        void (led::*action)(); 
        led *ld; 
    public: 
        auto_switch(void(led::*a)(), led*l) : action(a), ld(l) {}
        void go () { (ld->*action)(); }
    }; 
    // usage:  
    auto_switch s(&led::turn_off, &l1);
    s.go(); 
    

    Online demo

    Option 3 : the functional way (may that's what you're looking for ?)

    Another variant would be to use the standard functional library to bind a member function and the object on which it shall be executed (as well as any need parameters):

    class auto_switch{
        std::function<void()> action; 
    public: 
        auto_switch(function<void()>a) : action(a) {}
        void go () { action(); }
    }; 
    

    Here you can bind anything: any function of any class:

    auto_switch s(bind(&led::turn_off, l1));
    s.go(); 
    auto_switch s2(bind(&blinking_led::blink, l2));
    s2.go(); 
    

    Online demo

    Option 4 : command pattern

    Now if you want to perform something on an object when you turn on and off the switch, but you need total flexibility, you can just implement the command pattern : this lets you execute anything on any object. And you don't even need a function pointer.