如何使用引用捕获正确复制 lambda?

How to correctly copy a lambda with a reference capture?

好的,所以我 运行 在用 c++ 实现类似系统的 c# 属性 时遇到了问题(参见:)。

考虑以下示例:

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

If/when T运行sformCmp 的实例是 copied/moved(例如,如果它存储在 std::vector 中并调用了调整大小),现在引用捕获无效。

问题是我如何确保 copy/move 发生时我也更新了参考捕获?

我已经尝试为 属性 类 实现一个复制构造函数,但我 运行 遇到了同样的问题,可能是因为我没有做对。有什么想法吗?

更新:

我正在尝试对 Matthias Grün 建议的仿函数做一个类似的想法,基本上我将 T运行sformCmp 指针传递给 属性DelGetSet 构造函数,它将被传递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; });

但是,我需要能够更新存储在 属性DelGetSet 中的指针才能使这项工作正常进行。

您可以创建仿函数而不是使用 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{};
};

然后将这些实例传递给 PropertyDelGetSet。在复制和移动期间,您可以更新 m_pRef 指针以指向正确的实例,有点像这样:

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

假设 PropertyDelGetSet::getter() 将 return 一个 Getter& 通过它可以检索包含的仿函数。

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

下面是我们如何在我的家乡星球上执行此操作。

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,没有捕获,不需要更新任何东西,没有类型擦除的开销,什么都没有。

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

告诉你什么。 这个设计不行.

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

如果需要一个类似于 属性 的对象,例如与其他类似界面的统一,则即时创建一个,使用它,并在对象有机会移动之前将其丢弃。它没有作为对象的一部分存储的业务。关注点分离。

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

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

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

这样就不需要在 属性 类 中传递 TransformCmp 指针,这样会更简洁。如果有一种方法可以在重写后调用生成的复制构造函数,那就更干净了,但这对我来说已经很满意了。