Я заметил, что не могу изменять значения объектов, хранящихся внутри std::vector или std::map. Результат кода ниже дает "0" в результате вызова функции toggle() в обоих случаях. Такое поведение отсутствует при использовании объекта напрямую вне вектора. Мне что-то не хватает о std::vector или подобных контейнерах? Как правильно хранить объекты в контейнерах и сохранять возможность изменять их значения?

#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;
}

Выход :

clang++-7 -pthread -std=c++17 -o main main.cpp
./main
toggle .. from lambda 0
0
toggle .. from lambda 0
0
2
Xgo 10 Окт 2021 в 00:17

2 ответа

Лучший ответ

push_back(a) создает копию a. mythings[0] возвращает ссылку на эту копию. Таким образом, все, что было сделано для изменения членов mythings[0], не будет отражено в a, и наоборот.

Однако, когда a копируется push_back(), созданный компилятором конструктор копирования Thing копирует вектор myfuncs как есть, и поэтому лямбда, которая a} конструктор, хранящийся в этом векторе, также копируется как есть. Эта лямбда захватила указатель this, который указывает на a, а не на новую копию.

Итак, когда вы вызываете mythings[0].setValue(), вы изменяете value скопированного Thing, но когда вы вызываете mythings[0].toggle(), вы вызываете лямбду, которая печатает value of a - который изначально никогда не инициализировался , поэтому вывод будет неопределенным до тех пор, пока не будет вызван a.setValue().

Чтобы исправить это, вам необходимо реализовать собственный конструктор копирования для Thing, который хранит свою собственную лямбду, захватывающую скопированный указатель this Thing, а не хранит копию более ранней лямбды из Thing копируется, например:

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);
}

Онлайн-демонстрация

2
Remy Lebeau 9 Окт 2021 в 22:17

Я пробовал это также на основе комментариев. Честно говоря, мне это не нравится. Слишком много скриптов для вызова toggle ()

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

class Thing;

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

class Thing: public OtherThing{
public:
Thing(){
    std::function<int(Thing*)> myfunc= [](Thing* obj)->int{
        std::cout << "from lambda " << obj->value << std::endl;
        return obj->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;
    
    
    // copy is here
    mythings.push_back(a);
    
    // test
    mythings[0].setValue(666);
    mythings[0].toggle(&mythings[0]);
    mythings[0].setValue(999);
    mythings[0].toggle(&mythings[0]);

    return 0;
}

Выход:

toggle .. from lambda 666
666
toggle .. from lambda 999
999
0
Xgo 10 Окт 2021 в 12:33