Class instances not retaining data

I’m running into snags where class instances that I’m storing within a management class are not retaining their data. I wrote out the core concepts that I’m wrestling with into an example here: https://onlinegdb.com/KFqNSz2r6

I have an Action class that acts as a container for some arbitrary action. It’s primary function is to act as a state machine to facilitate non-blocking code form a microcontroller project:

class Action {
public:
    Action(int interval ,funcp_t callback ) : _interval(interval) , _callback(callback)
    {
    }
    
    bool trigger(int now) {
        if (now - _lastCheck > _interval) {
            _lastCheck = now;
            return true;
        }
        cout << "now: " << now;
        cout << ", last: " << _lastCheck;
        cout << ", interval: " << _interval;
        cout << ", diff: " << (now - _lastCheck);
        cout << endl;
        return false;
    }
    
    void fire() {
         cout << "action fired. last check: " << _lastCheck << endl;
        _callback();
    }

    
private:
  int _interval = 0;
  int _lastCheck = 0;
  funcp_t _callback;
};

I also have an ActionManager class that acts as a container and orchestrator class for the actions. The tick method is called to advance the state machines for all actions:

class ActionManager {
public:
   void addAction(Action action) {
       _actions.push_back(action);
   }
   
   void tick() {
       int now = millis();
       
       
       for(auto action : _actions) {
           if(action.trigger(now)) {
               action.fire();
           }
       }
   }
   
private:
  vector<Action> _actions;
};

In the rest of my setup code I’m setting up the action manager, adding actions, and then starting an infinite loop (to emulate an arduino loop) to advance the ticks of the ActionManager:

void sayHi() {
    cout << "oh hi" << endl;
}
void sayHey() {
    cout << "hey there" << endl;
}

ActionManager manager = ActionManager();
Action hiAction = Action(5000, sayHi);
Action heyAction = Action(1000, sayHey);

int main()
{
    cout<<"--> Start <--" << endl;
    manager.addAction(hiAction);
    manager.addAction(heyAction);
    
    
    while(true){
        manager.tick();
        cout << endl;
    }
    
    return 0;
}

The problem I’m running into is that the _lastCheck property of the Action instances is never updated. The console is reading:

–> Start <–

now: 65, last: 0, interval: 5000, diff: 65

now: 65, last: 0, interval: 1000, diff: 65

now: 86, last: 0, interval: 5000, diff: 86

now: 86, last: 0, interval: 1000, diff: 86

now: 98, last: 0, interval: 5000, diff: 98

now: 98, last: 0, interval: 1000, diff: 98

now: 107, last: 0, interval: 5000, diff: 107

now: 107, last: 0, interval: 1000, diff: 107

now: 118, last: 0, interval: 5000, diff: 118

now: 118, last: 0, interval: 1000, diff: 118

Note that the last check value being printed out never changes.

I’m pretty sure that this issue is happening because when I call addAction, my actions are being passed by value instead of reference so tried storing the actions as a vector of pointers:

class ActionManager {
public:
   void addAction(Action *action) { // accept a pointer to an action
       _actions.push_back(action);
   }
   
   void tick() {
       int now = millis();
       
       
       for(auto action : _actions) {
           if(action->trigger(now)) {
               action->fire();
           }
       }
   }
   
private:
  vector<Action *> _actions; // store a vector of actions
};

...

manager.addAction(&hiAction); // add by passing in the address of the action
manager.addAction(&heyAction);
    

But I still get nothing stored in the last check.

I feel like I’m missing something simple and I’ve just been working around the problem for too long to see it. Any ideas?

Answer

In your original version, the problem is that when you iterate over the actions, you make copies of the actions instead of referencing them.
In for (auto action: _actions) the action is copied. Use for (auto&& action: _actions) instead.

The version using pointers, doesn’t have that problem and once the difference between now and _lastCheck becomes greater than _interval, _lastCheck should be updated.