c++ 为什么存储在std::map中的“setter”函数不能正确设置值?

mcvgt66p  于 2023-10-21  发布在  其他
关注(0)|答案(1)|浏览(128)

我正在尝试编写一个基本的游戏引擎,并且遇到了一个关于动态组件创建的问题。我这里有一个最小的工作示例:

#include <bitset>
#include <variant>
#include <map>
#include <functional>

#include <iostream>

using Property = std::variant<int, unsigned int, bool, double, std::string>;

class Component
{
public:
    std::map<std::string, std::function<void(Property)>> _setters;
    Component() {}

    virtual void DestroyComponent() {}
};

class Transform : public Component {
    public: static std::string name() { return "Transform"; }
    void __set_x(Property p) {
        x = std::get<double>(p);
    }
    public: double x = 0;
    
    public: Transform() {
        _setters["x"] = [=](Property p){this->__set_x(p); };
    }
};

int main()
{
    std::vector<Transform> ts = {Transform()};
    Component* c = &ts[0];

    c->_setters["x"](3.0);
    std::cout << ts[0].x << std::endl;

    return 0;
}

(Note格式方面的任何怪异都来自于这样一个事实,即为了方便起见,我的大多数组件都是用大量宏定义的。)
这将结束打印0并返回,当它应该打印3时。
现在的问题是我有一个子类的组件列表(这里表示为一个转换向量),我需要从中获取一个。在运行时,我不知道这个类是什么,所以我通过引用元素的索引作为Component指针来获取它。当我的代码调用_setters[<property>]链接的函数时,它确实按预期进入了函数(通过将print函数放在lambda和called函数中来证明),但值从未设置。通过调试,我还向自己证明了引用向量的索引会产生正确的内存地址,因此指针应该是正确的。所以我的问题是为什么没有正确设置值,我该如何修复它?

xpszyzbs

xpszyzbs1#

问题是捕获this然后复制Transform对象。
Transform()构造函数中,表达式_setters["x"] = [=](Property p){this->__set_x(p); };捕获this。这捕获了this在捕获发生时的立即值,它不能捕获“当前对象”的概念。
当你构造ts时,你使用了一个初始化器列表,它需要复制初始化器。在这个复制过程中,_setters被复制,包括由原始对象的默认构造函数设置的setter。它捕获的this仍然指向原始对象。
后来,当您调用c->_setters["x"](3.0);时,您成功地运行了c所指向的Transform的setter,但是这个setter是通过复制另一个Transform获得的。setter捕获的this仍然指向初始化列表中的原始示例。它不指向c。setter试图为一个不再存在的示例设置x的值,导致Undefined Behaviour。
一个可能的解决方案是实现一个复制构造函数,它覆盖"x" setter来捕获新示例的this。范例:

Transform(Transform const& copy) : Component(copy) {
    _setters["x"] = [=](Property p){this->__set_x(p); };
}

但是,这种解决方案会重复代码,并且在您自己在外部覆盖"x"的情况下可能会很脆弱。您应该检查您的设计,可能会向setter添加一个“this”参数,以便任何示例都可以将自己的this传递给该函数。

相关问题