【问题标题】:How to correctly copy a lambda with a reference capture?如何正确复制带有引用捕获的 lambda?
【发布时间】:2021-07-28 11:46:06
【问题描述】:

好的,所以我在 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 的实例时(例如,如果它存储在 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 中的指针才能使其正常工作。

【问题讨论】:

  • 由于您无法直接访问实现引用捕获的数据,因此您无法“更新”引用捕获。因此,您将不得不重新创建 lambda。或者您不使用 lambdas(但自己实现闭包类型)。更一般地说,您已经创建了一个自引用类型。
  • 我可以使用复制构造函数重新创建 lambda 吗?

标签: c++11 lambda copy-constructor move-constructor


【解决方案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{};
};

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

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

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

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

【讨论】:

  • 我会看看这个,但我更喜欢使用 lambdas
  • 我将如何更新 m_pRef 指针?我已经更新了我的问题,因为我认为我可以在保留 lambdas 的同时做类似的事情
  • 当然,始终将正确的实例传递给 lambda 的解决方案也是一个可行的解决方案!
  • 得到它的工作哈哈看起来真的很难看,因为我必须在复制构造函数中重新构造 PropertyDelGetSet 实例,这相当冗长且不干净。但是,现在它正在工作,我将尝试清理它!非常感谢您的帮助:)
  • 如果有帮助,请考虑将答案标记为正确:-)
【解决方案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,没有捕获,不需要更新任何东西,没有类型擦除的开销,什么都没有。

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

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

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

如果需要一个类似属性的对象,例如为了与其他类似接口的统一,那么在对象有机会移动之前动态创建一个、使用它并放下它。它没有业务被存储为对象的一部分。关注点分离。

【讨论】:

  • 我之所以使用属性类,是因为它与一个脚本系统绑定在一起,该系统自动将成员绑定到脚本语言,并且它知道如何绑定属性类以及是否是get-set或仅获得。
  • P.S.在我的星球上,我们已经找到了解决方案:)
【解决方案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 指针,这样更简洁。如果有办法在覆盖后也调用生成的复制构造函数,那就更干净了,但这对我来说还是很满意的。

【讨论】:

  • 我接受这个作为答案,但如果可以改进,请告诉我!
猜你喜欢
  • 1970-01-01
  • 2020-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-28
  • 2011-09-25
相关资源
最近更新 更多