Store member- or lamda-functions into a member variable

See Post #16.

Let's try storing an entire class in another class

class TrafficLight {
  public:
    int LightColor  = 0;
    uint32_t Offset = 0;
    void TrafficLightOffset(uint32_t TimeOffset){Offset=TimeOffset;};
    void TrafficLightTimer();
    int TrafficLightColor(){return LightColor;};
};

void TrafficLight::TrafficLightTimer() {
  LightColor = ((uint32_t)abs(((float)millis() + Offset) * .001)) % 3;
}; // every  Seconds change colors

class Car  {
  public:
    TrafficLight *TrafficLT;
    //  TrafficLT = new TrafficLight; // a place to hold the class
    void SetActiveTraficLIght(TrafficLight *TL_PTR) {
      TrafficLT = TL_PTR;  // Store the given class here
    }; // Store the Traffic Light we are approching next
    int TLColor(){if(TrafficLT)return(TrafficLT->TrafficLightColor());else return(-1);};
};

//Define the classes here
TrafficLight TL1;
TrafficLight TL2;
Car Ford;
Car Dodge;

void setup() {
  Serial.begin(115200);
  TL1.TrafficLightOffset(1000);
  TL2.TrafficLightOffset(5000);
  Ford.SetActiveTraficLIght(&TL1);
  Dodge.SetActiveTraficLIght(&TL2);
}

void loop() {
  TL1.TrafficLightTimer();
  TL2.TrafficLightTimer();
  if(Ford.TLColor() == 0){
    Serial.print("Ford Light is Green    &");
  }
  if(Dodge.TLColor() == 0){
    Serial.println("    Dodge Light is Green");
  }
  if(Ford.TLColor() == 1){
    Serial.print("Ford Light is Yellow    &");
  }
  if(Dodge.TLColor() == 1){
    Serial.println("    Dodge Light is Yellow");
  }
    if(Ford.TLColor() == 2){
    Serial.print("Ford Light is RED    &");
  }
  if(Dodge.TLColor() == 2){
    Serial.println("    Dodge Light is RED");
  }
  static unsigned long Timer;
  if ((millis() - Timer) >= (10000)) {  // after 10 seconds
    Timer = millis();
    Ford.SetActiveTraficLIght(&TL2);// make it the same light as the Dodge
  }
}

instead of storing just a member of the class I just stored a pointer to the desired traffic light class in the car class

is this what you are thinking?

As @gfvalvo already mentioned, std::function would be the most simple/elegant solution to your problem. However, it is quite expensive memory wise and uses dynamic memory allocation. Both might be prohibitive for application in micro controller applications.

I tried to work around this problem by doing a "callbackHelper" class

which, like std::function, is able to store more or less anything which can be called. E.g. free functions, capturing and non capturing lambda expressions, functors etc. I originally did this for the Teensy boards, but the code is sufficiently generic to run on other boards as well. Besides the teensies I tested a Seed XIAO, and a nucleo F401Re. But it should work on any c++11 compatible system. (It doesn't work on the AVR-GCC (uno etc)).

#include "Arduino.h"
#include "CallbackHelper.h"

// Free function callback ------------------------------------------------------
void freeFunction()
{
    Serial.println("Free function callback");
}

// Using a non static member function as callback -------------------------------
class Test
{
 public:
    Test(int _i)
    {
        i = _i;
    }

    void someMemberFunction(float f)
    {
        Serial.printf("non static member function i=%f\n", i * f);
    }

    int i;
};
Test test(42);

// Using a functor as callback----------------------------------------------------
class MyFunctor_t
{
 public:
    void set(int _i)
    {
        i = _i;
    }

    void operator()() const
    {
        Serial.printf("Functor i=%d\n", i);
    }

 protected:
    int i = 0;
};
MyFunctor_t functor;

//------------------------------------------------------

// some aliases to save typing
using callbackHelper_t = CallbackHelper<void(void), 5>; // handles 5 slots for void(void) type callbacks
using callback_t       = callbackHelper_t::callback_t;  // type of the callbacks

callbackHelper_t cbh; // helps to generate callbacks from various parameters (function pointers, lambdas, functors...)
callback_t* cb[5];    // array to store pointers to the generated callbacks

void setup()
{
    // generate various callbacks and store pointers to them in the cb array
    cb[0] = cbh.makeCallback(freeFunction, 0);                                    // free function -> to callback_t
    cb[1] = cbh.makeCallback(functor, 1);                                         // functor -> callback_t
    cb[2] = cbh.makeCallback([] { Serial.printf("non capturing lambda\n"); }, 2); // simple, non capturing lambda expression -> callback_t
    cb[3] = cbh.makeCallback([] { test.someMemberFunction(3.1415); }, 3);         // non static member function -> callback_t

    int i = 42;
    cb[4] = cbh.makeCallback([i] { Serial.printf("capturing lambda i=%d\n", i); }, 4); // lambda capturing a variable as callback
}

void loop()
{
    for (int i = 0; i < 5; i++)
    {
        cb[i]->invoke(); // invoke stored callback
    }
    Serial.println();

    test.i = millis();     // change state of the test class on the fly
    functor.set(millis()); // change state of the functor on the fly
    delay(500);
}

Since this library is quite new (2 weeks) there might (and probably will) be bugs and incompatibilties. In case you want to give it a try and stumble over something or need help feel free to ask here or open an issue on gitHub.

There is also attachInteruptEx and IntervalTimerEx (teensy only) which show how to use the CallbackHelper in real life applications.

1 Like

Are you sure about that? I can't understand why 'std::function' would need dynamic memory allocation since it's basically just a wrapper for various flavors of function pointers. FWIW, on a Teensy 3.2, 'sizeof(std::function<void()>)' is just 16 bytes. So if it doesn't use dynamic memory allocation, it's memory footprint is pretty small.

Here is how I understand it: Besides simple function pointers, std::function also accepts functors, i.e. classes with an overloaded operator()(). Depending on its member variables a functor can have any size. Since std::function keeps a copy of the callable object (with unknown size) it doesn't have much chance other than constructing it on the heap or in some preallocated static memory. AFAIK std::function maintains such a preallocated memory for small objecs (function pointers, small functors, simple lambdas...) but resorts to dynamic memory allocation for larger objects.

The same is valid for lambda expressions. For each lambda the compiler generates an anonymous functor. E.g., for some lambda

float x, y;
int p;
[x, y, p](float a, float b) { return p * (x + y) / (a * b); };

The compiler basically generates something like

struct anonymous
{
    float operator()(float a, float b)
    {
        return p * (x + y) / (a * b);
    }
    const double x, y;
    const int p;
};

Depending on the size of the captured variables, the generated class can have any size which is difficult to store statically.

In embedded applications the lambdas tend to be very simple. Typically only the this pointer or a few variables are captured and the autogenerated functors are quite small. Therefore, the CallbackHelper library statically reserves a fixed size static memory pool (default: 16bytes per slot) where it stores the callable objects. If the user tries to store a larger object (lambda, functor..) it doesn't resort to dynamic allocation but generates a compiletime error.

FWIW, on a Teensy 3.2, 'sizeof(std::function<void()>)' is just 16 bytes. So if it doesn't use dynamic memory allocation, it's memory footprint is pretty small.

Yes, probably they need to allocate memory from the heap for objects larger than some 16bytes then, which rarely happens.

I'm more concerned about the general memory overhead generated by using std::function.
This simple code:

#include <functional>

void freeFunction(){
    digitalToggleFast(LED_BUILTIN);
}

std::function<void(void)> callback;  // switch between std::function and a simple function pointer
//void (*callback)();

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    callback = freeFunction;
}

void loop(){
    callback();
    delay(250);
}

generates the following memory footprint on a T3.2

FUNCTION POINTER VERSION
Memory region         Used Size  Region Size  %age Used
        FLASH:        6312 B       256 KB      2.41%
          RAM:        2740 B        64 KB      4.18%

std::function VERSION
Memory region         Used Size  Region Size  %age Used
        FLASH:       75244 B       256 KB     28.70%
          RAM:        4892 B        64 KB      7.46%

Which often is not acceptable.

Sorry for the late response I had todo a lot of stuff so I did not have time to investigate the problem further. Maybe may problem is more cleare when I poste the whole code since creating a mre does not make my problem clear

I am trying to create a scheduler for my tasks since I need to handle alot of stuff recurrent but not all are equally imported. The easiest solution would be to put all in a loop and execute some task more often as other ones but that is pretty messy and I can not implement better scheduling strategies.

I hope I can try this solutions on the weekend but I have sadly very little time for this at the moment.
Scheduler.cpp (1,1 KB)
Scheduler.h (563 Bytes)
SimpleTask.cpp (274 Bytes)
SimpleTask.h (448 Bytes)

Task.h (1023 Bytes)
Task.cpp (474 Bytes)

LinkedListEntry.h (925 Bytes)
LinkedList.h (4,3 KB)

What kind of "Arduino" board?

I use an ESP32(S2/C3) currently but maybe I switch to ESP32S3 due to some extra hardware but it is more expensive as the S2/C3 so I am not sure if it is worth it.

Then why are you trying to create your own task scheduler? All ESP32s run a full-featured, multi-tasking, Real Time Operating System called FreeRTOS. It's always there even when using the Arduino Core.

1 Like

Because I am did not know that Arduino gives me that option. Is there a good tutorial which you can recommend?
I have some questions maybe you can answer them for me:
Can I define a task which should be only executed once when the timer reached the deadline? ( Or only once when an even occurs?)
Can I stop executing a task if the program/user does not need it any more
Can I define a task which can be executed repeted every e.g hour/day/weekday or are the timespans too big?

And I still need a "Managerclass" which can add/remove new tasks dynamicly (Common interface) can I do something like that with FreeRTOS?

(My scheduler is more an event/time-handler in my case or a psydo-sheduler i guess)
But if I could use the FreeRTOS as underlying scheduler that would be greate too.

Yes to all. I won't be at computer for a few days. So can't post any references till back. But, Google knows.

Yes I already duckduckgoed it :smiley: but I didn“t find every thing I need so far.

The documents here are good references for vanilla FreeRTOS: https://www.freertos.org/Documentation/RTOS_book.html

The specializations for ESP32 FreeRTOS are described in the API Guide: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/freertos-smp.html

1 Like

Thank you, I now found some tutorials which helped me to get started with it.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.