如何正确复制带有引用捕获的 lambda?

How to correctly copy a lambda with a reference capture?

提问人:Belfer4 提问时间:7/28/2021 最后编辑:Belfer4 更新时间:7/29/2021 访问量:511

问:

好的,所以我在 c++ 中实现 system 等 c# 属性时遇到了问题(参见:https://stackoverflow.com/a/68557896/3339838)。

请看以下示例:

struct TransformCmp
{
    PropertyDelGetSet<vec3> position =
        PropertyDelGetSet<vec3>(
            [&]() -> const vec3& { return m_position; },
            [&](const vec3& val) { m_position = val; m_dirty = true; });

private:
    bool m_dirty = true;
    vec3 m_position = vec3(0);
}

如果/当 TransformCmp 的实例被复制/移动时(例如,如果它被存储在中并调用了 resize),则引用捕获现在无效。std::vector

问题是,我如何确保在复制/移动发生时,我也更新了引用捕获?

我尝试为 Property 类实现复制构造函数,但我遇到了同样的问题,可能是因为我没有正确操作。有什么想法吗?

更新:

我正在尝试对 Matthias Grün 建议的函子做一个类似的想法,基本上我正在将 TransformCmp 指针传递给 PropertyDelGetSet 构造函数,该构造函数将在 get-set 函数上传递。

初始化属性时,我正在做这样的事情:

PropertyDelGetSet<TransformCmp, vec3> position =
    PropertyDelGetSet<TransformCmp, vec3>(this, // <- now passing this
        [](TransformCmp* p) -> const vec3& { return p->m_position; },
        [](TransformCmp* p, const vec3& val) { p->m_position = val; p->m_dirty = false; });

但是,我需要能够更新存储在 PropertyDelGetSet 中的指针才能完成这项工作。

C++11 lambda 复制构造函数 move-constructor

评论

0赞 dyp 7/28/2021
由于您无权直接访问实现引用捕获的数据,因此无法“更新”引用捕获。因此,您必须重新创建 lambda。或者您不使用 lambda(而是自己实现闭包类型)。更一般地说,您已经创建了一个自引用类型。
0赞 Belfer4 7/28/2021
我可以使用复制构造函数重新创建 lambda 吗?

答:

1赞 Matthias Grün 7/28/2021 #1

您可以创建函子而不是使用 lambda,大致如下所示:

struct Getter {

    const vec3& operator()() const noexcept { return m_pRef->m_position; }

    TransformCmp* m_pRef{};
};

struct Setter {

    void operator()(const vec3& pos) noexcept { m_pRef->m_position = pos; }

    TransformCmp* m_pRef{};
};

然后将这些实例传递给 。在复制和移动过程中,您可以更新指针以指向正确的实例,如下所示:PropertyDelGetSetm_pRef

struct TransformCmp
{
   ...
   TransformCmp(const TransformCmp& other) : position{ other.position }
       position.getter().m_pRef = this;
   }
   ...
}

假设这将返回一个,通过该函数可以检索包含的函子。PropertyDelGetSet::getter()Getter&

无法从外部访问 Lambda 捕获,因为它们是 lambda 捕获的。private

评论

0赞 Belfer4 7/28/2021
我会看看这个,但我更喜欢使用 lambda
0赞 Belfer4 7/28/2021
如何更新m_pRef指针?我已经更新了我的问题,因为我认为我可以在保留 lambda 的同时做类似的事情
0赞 Matthias Grün 7/28/2021
当然,始终将正确的实例传递给 lambda 的解决方案也是一个可行的解决方案!
0赞 Belfer4 7/28/2021
让它工作起来哈哈看起来真的很丑,因为我必须在复制构造函数中重新构造 PropertyDelGetSet 实例,这相当冗长且不干净。但是,现在它正在工作,我将尝试清理它!非常感谢您的帮助:)
0赞 Matthias Grün 7/28/2021
如果有帮助,请考虑将答案标记为正确:-)
0赞 n. m. could be an AI 7/28/2021 #2

以下是我们在我的家乡星球上如何做到这一点。

struct TransformCmp
{
  void setPosition(const vec3& val) { m_position = val; m_dirty = true; }
  vec3 getPosition() { return m_position; }
private:
  bool m_dirty = true;
  vec3 m_position = vec3(0);
};

看?没有 lambda,没有捕获,无需更新任何内容,没有类型擦除的开销,什么都没有。

但!但!但!我的设计呢?我有一个设计!有属性!和模板!和东西!

告诉你什么。这种设计不好

拥有复杂的事物(“属性”)在内部管理对对象的引用本身并没有错。但是,如果你想将这些东西存储在对象本身中,这不会通过代码审查。该对象具有位置和脏标志。它们可以通过两个成员函数的绝对最小接口进行操作,并且绝对最小。

如果需要一个类似属性的对象,例如与其他类似接口的统一,那么可以动态创建一个,使用它,并在对象有机会移动之前将其删除。它没有业务作为对象的一部分进行存储。关注点分离。

评论

0赞 Belfer4 7/28/2021
我之所以使用属性类,是因为它与一个脚本系统绑定,该系统会自动将成员绑定到脚本语言,并且它知道如何绑定属性类以及它是 get-set 还是 get only。
0赞 Belfer4 7/28/2021
P.S. 在我的星球上,我们已经找到了解决方案:)
0赞 Belfer4 7/28/2021 #3

我想我已经找到了最好的解决方案。

这个想法是从复制构造函数调用构造函数,然后手动设置可以简单复制的其余成员:

struct TransformCmp
{
    TransformCmp() {}

    TransformCmp(const TransformCmp& other)
        : TransformCmp() // Makes sure the lambda refs are updated
    {
        // Trival copy of members
        m_dirty = other.m_dirty;
        m_position = other.m_position;
    }

    PropertyDelGetSet<vec3> position =
        PropertyDelGetSet<vec3>(
            [&]() -> const vec3& { return m_position; },
            [&](const vec3& val) { m_position = val; m_dirty = false; });

private:
    bool m_dirty = true;
    vec3 m_position = vec3(0);
};

这样,就不需要在 Property 类中传递 TransformCmp 指针,这要干净得多。如果有一种方法可以在覆盖生成的复制构造函数后调用它,它会更干净,但这对我来说是相当令人满意的。

评论

0赞 Belfer4 7/31/2021
我接受这个答案,但如果可以改进,请告诉我!