andizen
andizen

Reputation: 11

Updating state of class members does not deliver correct values

I ran into a little problem here, and I'm kind of stuck, tried googling it but couldn't find a solution that helped. Hopefully somebody here can help.

So I have this little class here. I'm trying to initialize Button members in a loop and push the created members to a vector named btns. I thought this way I can update the button members in a method by just looping through the btns vector and call each of the members update function. The button.update() function basically just sets a state to this point.

So far this works quite good, or at least every Button gets its correct init values. I run into the weird behaviour when I want to call the button.update() function. As I already mentioned, every button has a boolean member called "state" that gets initialized with the value false. For the test purpose, I tried to set the state of a button to true with the button.update() method. The problem is now, when I try to check if the update worked and read back the value of state, I only get back true if I call the button.update() method in the same scope as the read back. If I want to get the value of state later, it comes back as false, and I really can't understand or explain why.

This is the class I'm talking of:

#include <ucRP2040.hpp>

ucRP2040::ucRP2040(std::vector<u8_t> btns, std::vector<u8_t> leds)
{
    for (u8_t pin : btns)
    {
        Button btn(pin);
        // btns here is a std::vector<Button>
        this->btns.push_back(btn);
    }
}

void ucRP2040::update()
{
    /*Input*/
    for (Button btn : btns)
    {
        btn.update();
    }

    for (Button btn : btns)
    {
        // btn.update(); <- if I uncomment this line, state works    
        Serial.print(" state: ");
        //only works if btn.update() was called in loop scope 
        Serial.println(btn.state); //gives back false       

        //This works correct
        Serial.print("size: ");
        Serial.print(btns.size());
        Serial.print(" pin: ");
        Serial.print(btn.pin);
    }
 
    // This does not work at all, gives back false
    Serial.println(btn[0].state);

    delay(500);

}

This is the Button class:

#include <Button.hpp>

Button::Button(u8_t pin) : pin{pin}, state{false}
{
}

void Button::update()
{

    state = true;
}

This is my main:

#include <ucRP2040.hpp>
#include <Arduino.h>

// USER BUTTON abd LEDs
std::vector<u8_t> usrbtns = {13, 14, 15};
std::vector<u8_t> usrleds = {22, 23, 24, 25};

// INIT HW
ucRP2040 ucrp2040(usrbtns, usrleds);

void setup()
{
    Serial.begin(9600);
}

void loop()
{
    ucrp2040.update();
}

I tried to break it down and leave comments in the code, maybe this is more self-explanatory. Has anybody a clue what I'm doing wrong?

Upvotes: 1

Views: 49

Answers (1)

Sam Varshavchik
Sam Varshavchik

Reputation: 118292

    for (Button btn : btns)

This is range iteration by value. In the for loop, the btn object is a copy of each value in the btns container.

btn.update();

// ...

void Button::update()
{

    state = true;
}

This modifies the btn object. And at the end of the loop, btn gets destroyed. It disappears. It becomes no more. It ceases to exist. It starts pining for the fjords. It becomes an ex-Button.

Instead, iteration by reference should be used in order to update each value in the array:

    for (Button &btn : btns)

Now, btn will be a reference to each value in the btns container, iterating over it.

The other for range iteration will likely need the same change. Even if it doesn't modify each Button object this will likely optimize out an utterly useless copy of each Button that happens when iterating by value.

This is no different than passing parameters to a function. As you know, parameters get passed by value, and each parameter in the function is a copy of the passed-in object; while a reference function parameter ends up passing by reference. Same thing.

Upvotes: 2

Related Questions