Home > Enterprise >  Changing values of objects in std vector c
Changing values of objects in std vector c

Time:10-10

I noticed that I am not able to change values of objects stored inside a std::vector or a std::map. The output of the code below gives "0" as a result of the toggle() function call in both cases. This behavior is not present while using the object directly, outside of the vector. Am I missing something about std::vector or similar containers? How do we properly store objects in containers and keep the ability to change their values?

#include <iostream>
#include <functional>
#include <vector>

class OtherThing{
public:
void addElement(std::function<int()> func){
    this->myfuncs.push_back(func);
}
void toggle(){
    std::cout << "toggle .. " << this->myfuncs[0]() << std::endl;
}
private:
std::vector<std::function<int()>> myfuncs; 
};

class Thing: public OtherThing{
public:
Thing(){
    std::function<int()> myfunc= [this]()->int{
        std::cout << "from lambda " << this->value << std::endl;
        return this->value;
        };
    this->addElement(myfunc);
    }
void setValue(int value){
    this->value = value;
}
int getValue(){
    return this->value;
}
private:
int value;
};


int main() {
    // container for things 
    std::vector<Thing> mythings;
    // a thing
    Thing a;
    
    mythings.push_back(a);
    
    mythings[0].setValue(666);
    
    mythings[0].toggle();
    mythings[0].setValue(999);
    mythings[0].toggle();
    return 0;
}

output :

clang  -7 -pthread -std=c  17 -o main main.cpp
./main
toggle .. from lambda 0
0
toggle .. from lambda 0
0

CodePudding user response:

push_back(a) makes a copy of a. mythings[0] returns a reference to that copy. As such, anything done to modify the members of mythings[0] will not be reflected in a, and vice versa.

However, when a is copied by push_back(), Thing's compiler-generated copy constructor will copy the myfuncs vector as-is, and so the lambda that a's constructor had stored in that vector is also copied as-is. That lambda had captured a this pointer that points at a, not the new copy.

So, when you call mythings[0].setValue(), you are modifying the value of the copied Thing, but when you call mythings[0].toggle(), you are calling a lambda that prints the value of a - which was never initialized in the first place, so the output is indeterminate until a.setValue() is called.

To fix this, you need to implement your own copy constructor for Thing that stores its own lambda capturing the copied Thing's this pointer, not store a copy of the earlier lambda from the Thing being copied from, eg:

Thing(const Thing &src){
    value = src.value;
    std::function<int()> myfunc = [this]()->int{
        std::cout << "from copied lambda " << this->value << std::endl;
        return this->value;
    };
    this->addElement(myfunc);
}

Online Demo

  • Related