Callback function pointers C++ with/without classe

2020-06-09 06:56发布

问题:

I got stuck. I am trying to form a function that will eat classless function pointers and ones from objects. Here is my current code that hopefully explains more.

(It should run on a Arduino, so I cannot use big libraries.)

First off, I am using this library for the Arduino:

/* SimpleTimer - A timer library for Arduino.
 * Author: mromani@ottotecnica.com
 * Copyright (c) 2010 OTTOTECNICA Italy
 */

Which takes functions which it calls on a set timer interval of this type:

typedef void (*timer_callback)(void);

As far as my knowledge goes, it's a classles function, the webpage Pointers to member functions got me really far but, not far enough. Probably a terminology deficit on my side.

Now, I have made my own class which I would like in turn to use this SimpleTimer library. But if I feed the SimpleTimer my class functions, it does not like them (what I understand). But how would it be possible to make this happen without altering the SimpleTimer library.

So there is the class Robot, which has Robot::halt(). I want the robot to move forward for a set amount of time. Like so:

void Robot::forward(int speed, long time) {
    reset();
    timer.setTimer(time, c_func, 1);

    analogWrite(l_a, speed);
    analogWrite(r_a, speed);
    isMoving(true);
}

void Robot::halt() {
    __isMoving = false;
    digitalWrite(r_a, LOW);
    digitalWrite(r_b, LOW);
    digitalWrite(l_b, LOW);
    digitalWrite(l_a, LOW);
}

The c_func variable is a classless function at this point, but I would like to use the Robot::halt function. I have looked, read, learned but haven't succeeded yet. I just can't seem to wrap my head around this one because I am missing some angle.

I tried:

timer.setTimer(time, (this->*halt), 1);
timer.setTimer(time, Robot::*halt, 1);
timer.setTimer(time, &Robot::halt), 1);

But it would all amount to the same problem/ me just stabbing in the dark here...

EDIT

Earlier, I said not wanting to change the SimpleTimer library code. I want to comeback on this one, I guess altering it there would be the better option.

Thanks for all the current answers already, I was only allowed to flag one as a viable answer, actually everyhting I read here was extremely helpful.

To continue this, changing the SimpleTimer code. This class needs to have a reference to the object that holds my "halt" function, right? So, overloading the settimer function to something that takes my object and my function as two seperate pointers would work...? I think I am getting the hang of this but, I am not there yet with my head.

EDIT

I don't know who came with this one again but, anyone finding this thread. If found Member Function Pointers and the Fastest Possible C++ Delegates to give a very nice introduction in function pointers and member function pointers.

EDIT

Got it working, changed the SimpleTimer library to use this Delegate system: http://www.codeproject.com/KB/cpp/FastDelegate.aspx

It integrated very nicely, and it could be nice to have a standard Delegate system like this in the Arduino library.

Code as in test (working)

typedef

typedef FastDelegate0<> FuncDelegate;

Code in robot class:

void Robot::test(){
    FuncDelegate f_delegate;
    f_delegate = MakeDelegate(this, &Robot::halt);

    timer.setTimerDelg(1, f_delegate, 1);
}

void Robot::halt() {
    Serial.println("TEST");
}

Code in SimpleTimer class:

int SimpleTimer::setTimerDelg(long d, FuncDelegate f, int n){
    f();
}

Arduino prints TEST in the console.

Next step putting it in an array, don't see a lot of problems there. Thanks everyone, I can't believe the stuff I learned in two days.

What's that smell? Is that the smell of...? Success!

For the ones interested, the used Delegate system does not amount to memory capacity issues: With FastDelegate

AVR Memory Usage
----------------
Device: atmega2560

Program:   17178 bytes (6.6% Full)
(.text + .data + .bootloader)

Data:       1292 bytes (15.8% Full)
(.data + .bss + .noinit)


Finished building: sizedummy

Without FastDelegate:

AVR Memory Usage
----------------
Device: atmega2560

Program:   17030 bytes (6.5% Full)
(.text + .data + .bootloader)

Data:       1292 bytes (15.8% Full)
(.data + .bss + .noinit)


Finished building: sizedummy

回答1:

You can do this by making a functor object, that acts as a proxy between the timer code and your code.

class MyHaltStruct
{
public:
    MyHaltStruct(Robot &robot)
        : m_robot(robot)
        { }

    void operator()()
        { robot.halt(); }

private:
    Robot &m_robot;
}

// ...

timer.setTimer(time, MyHaltStruct(*this), 1);

Edit

If it can't be done via a functor object, you could global variables and functions instead, maybe in a namespace:

namespace my_robot_halter
{
    Robot *robot = 0;

    void halt()
    {
        if (robot)
            robot->halt();
    }
}

// ...

my_robot_halter::robot = this;
timer.setTimer(time, my_robot_halter::halt, 1);

This only works if you have one robot instance though.



回答2:

Since the timer callback signature doesn't take any argument, you unfortunately need to use some global (or static) state:

Robot *global_robot_for_timer;
void robot_halt_callback()
{
    global_robot_for_timer->halt();
}

you can at least wrap that lot into it's own file, but it isn't pretty. As Matthew Murdoch suggested, it might be better to edit the SimpleTimer itself. A more conventional interface would be:

typedef void (*timer_callback)(void *);

SimpleTimer::setTimer(long time, timer_callback f, void *data);

void robot_halt_callback(void *data)
{
    Robot *r = (Robot *)data;
    r->halt();
}

ie, when you call setTimer, you provide an argument which is passed back to the callback.

The smallest change to SimpleTimer would be something like:

SimpleTimer.h

typedef void (*timer_function)(void *);
struct timer_callback {
    timer_function func;
    void *arg;
};
// ... every method taking a callback should look like this:
int SimpleTimer::setTimeout(long, timer_function, void *);

SimpleTimer.cpp

// ... callbacks is now an array of structures
callbacks[i] = {0};

// ... findFirstFreeSlot
if (callbacks[i].func == 0) {

// ... SimpleTimer::setTimer can take the timer_callback structure, but
// that means it's callers have to construct it ...
int SimpleTimer::setTimeout(long d, timer_function func, void *arg) {
    timer_callback cb = {func, arg};
    return setTimer(d, cb, RUN_ONCE);
}


回答3:

You can't pass a non-static member function there - only a static one. The signature should be like this:

static void halt()
{
    //implementation
}

the reason is that each non-static member function has an implicit Robot* parameter known as this pointer which facilitates access to the current object. Since the callback signature doesn't have such Robot* parameter you can't possibly pass a member function of class Robot unless it is static.

So that in your implementation

void halt();

is in effect

static void halt( Robot* thisPointer );

and when you do

void Robot::halt() {
    __isMoving = false;
}

you effectively have this:

void Robot::halt( Robot* thisPointer ) {
    thisPointer->__isMoving = false;
}

and of course a halt( Robot*) function pointer can't be passed in place of void (*)(void) C callback function.

And yes, if you need access to non-static member variables of class Robot from inside the callback you'll have to somehow retrieve the pointer to class Robot instance elsewhere - for example, store it as a static member variable so that you don't rely on this pointer.



回答4:

It's important to understand that function pointers and pointers to class members are different not for an arbitrary reason but the fact that instance methods have an implicit this argument (also, they have to work with inherited and virtual functions, which adds even more complexity; hence they can be 16 or more bytes in size). In other words, a function pointer to a class member is only meaningful together with an instance of the class.

As the currently-top answer says, your best bet is to go with functors. While the setTimer function might only accept function pointers, it is possible to write a template function to wrap the call and accept both. For even more fine-grained processing, you can write a template metaprogram (Boost.TypeTraits has is_pointer, is_function and even is_member_function_pointer) to handle the different cases.

How you make the functors is a different story. You can opt for writing them by hand (which means implementing a class with operator() for each one of them), but depending on your needs that might be tedious. A couple of options:

  • std::bind: you can use it to create a functor whose first parameter will be bound to the value you specify - in the case of member functions, it will be the instance.
  • Depending on your compiler, you might not have access to std::bind - in this case I suggest boost::bind. It is a header-only library and provides the same functionality.
  • You can use another delegate implementation. I don't have experience with this one, but claims to be faster than other implementations (including std::function).

The mentioned libraries are header-only, so they probably don't count as "big libraries".