0

I'm running a bit of code on an Arduino machine that uses C++. The setup (very roughly) is like below in the main function. The example is fairly contrived, and the code design doesn't really pass the smell check, but it's the best I can do.

There's a 3rd party WiFi library that I'm trying to add an "onDisconnect" hook to. When my TaskRunner class executes, I attach a function to this hook so that when the WiFi disconnects, my task runner is notified.

The challenge is I don't know the language well enough to assign an anonymous function that also keeps the "isWifiConnected in scope. I'm somewhat learning C++ on the fly so please feel free to change the title of the question as I might not even be asking the right one.

Note that I may not be looking for an anonymous function. I'm trying to update the isWifiConnected property when onDisconnect() is called.

#include <iostream>
using namespace std;


// A 3rd Party class
// https://github.com/Hieromon/AutoConnect/blob/master/src/AutoConnect.h
// https://github.com/Hieromon/AutoConnect/blob/master/src/AutoConnect.cpp
class AutoConnect {
     public:
        AutoConnect(){};

        // I add this to the class as a hook to be called at about
        // line 549 code block where AutoConnect calls disconnect code
        void (*onDisconnect)();
};



void onDisconnect(){
    cout << "Disconnecting!" << endl;
    cout << "But how to make isWifiConnected false?  I don't have access :/";
};


// My task runner 
class TaskRunner {

    public:
        bool isWifiConnected;
        AutoConnect *connector;

        TaskRunner(AutoConnect & connector){
           this->connector = & connector; 
        }

        void execute(){

            isWifiConnected = true;
            // run some WiFi connection code ...

            // assign the onDisconnect hook to be run when it disconnects
            connector -> onDisconnect = onDisconnect;

            // but how can I update "isWifiConnected" = false when the onDisconnect runs

            // In JavaScript, I could do
            /*
                connector -> onDisconnect = function(){
                    // variable stays in scope due to closure.
                    isWifiConnected = false;
                }
            */
        }
};




   int main() {
        cout<<"Running ..." << endl;

        // Instantiate my classes and inject WifiConnector into my task runner
        AutoConnect connector;
        TaskRunner runner = TaskRunner(connector);

        // Simulate an event loop for effect
        for(int i = 0; i < 10; i++){
            if(i == 0) runner.execute();
            // on some other condition, Wifi is disconnected
            if(i == 9)  connector.onDisconnect();
        }

        return 0;
    }

Any ideas on how to update the TaskRunner's isWifiConnected variable? I've tried various pointer combinations but can't quite get it right.

Will Lovett
  • 1,241
  • 3
  • 18
  • 35
  • 1
    Sounds like you're interested in [Lambda Expressions](https://en.cppreference.com/w/cpp/language/lambda), but I'm with walnut. We need more info. – user4581301 Feb 07 '20 at 23:47
  • 1
    anonymous functions in C++ are called lambdas and require C++11. Issues is that only state-less lambdas can be bound to pointer, some template use \proxy is required there, e.g. std::function. Not sure if your platform got support, new ones do – Swift - Friday Pie Feb 07 '20 at 23:47
  • 2
    Note: C++ is a hard language to pick up willy-nilly. You'll probably save time in the long run with a bit of guidance from [a good book or two](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). – user4581301 Feb 07 '20 at 23:49
  • 1
    `this->connector = & connector;` is poor style -- conventionally, if an object is passed by reference then the called function should not obtain a persistent handle to the object. The caller might destroy the object after the call – M.M Feb 07 '20 at 23:52
  • @walnut On Arduino, I can download a local copy of their class and modify it. So I'm adding a function pointer to their class definition that I can call when they try to disconnect. – Will Lovett Feb 07 '20 at 23:52
  • Is the event going to be fired from a different thread than `main` thread? – M.M Feb 07 '20 at 23:53
  • @walnut https://github.com/Hieromon/AutoConnect/blob/master/src/AutoConnect.cpp line 549 code block. I think when I hit upload on the Arduino IDE it recompiles the classes. – Will Lovett Feb 07 '20 at 23:55
  • @walnut Sorry, I tried to simplify my code example as much as possible. The WifiConnector class is AutoConnect. I'm adding the onDisconnect to AutoConnect and then calling it at line 549's code block where AutoConnect runs its disconnect code. – Will Lovett Feb 08 '20 at 00:02
  • Thanks @walnut Just not sure how else to add some kind of notification or event listener to the class. The lifetime should be good. The task runner is at the very top of the food chain and lives as long as main does. I'll see what I can do to post a solution after I wade through some Lambda examples. – Will Lovett Feb 08 '20 at 00:20
  • @WillLovett See my answer, the TaskRunner object is destroyed before AutoConnect is, so there is a possibility of the lambda being called in-between, i.e. by the destructor of AutoConnect. – walnut Feb 08 '20 at 00:30

1 Answers1

1

Other issues with the code aside (see question comments):

You can store a lambda in a std::function:

Instead of void (*onDisconnect)(); declare it std::function<void()> onDisconnect;. (requires #include<functional>)

Then you can store a capturing lambda in it:

connector->onDisconnect = [this](){
    isWifiConnected = false;
};

Since this stores a pointer to *this, you must make sure the the TaskRunner object outlives any potential call to this hook/lambda. Otherwise your program will have undefined behavior.

In particular currently the TaskRunner is declared after the AutoConnect object in main, meaning that the latter will be destroyed before the AutoConnect and therefore there will be a possibility of the lambda being called when TaskRunner has already been destroyed. This is particularly the case if AutoConnect's destructor may call the lambda. Whether it does or not I don't know.

walnut
  • 21,629
  • 4
  • 23
  • 59