提问人:Belfer4 提问时间:7/28/2021 最后编辑:Belfer4 更新时间:7/29/2021 访问量:511
如何正确复制带有引用捕获的 lambda?
How to correctly copy a lambda with a reference capture?
问:
好的,所以我在 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 中的指针才能完成这项工作。
答:
您可以创建函子而不是使用 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&
无法从外部访问 Lambda 捕获,因为它们是 lambda 捕获的。private
评论
以下是我们在我的家乡星球上如何做到这一点。
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);
};
这样,就不需要在 Property 类中传递 TransformCmp 指针,这要干净得多。如果有一种方法可以在覆盖生成的复制构造函数后调用它,它会更干净,但这对我来说是相当令人满意的。
评论