复合 属性 对象,在更改所有子对象时抑制多个信号发射
Composite property object that inhibits multiple signal emissions when all subobjects are changed
我有一个简单的 property<T>
class 和 value_changed<T>
你可以 connect
/disconnect
接收或禁止事件 value_changed<T>::emit(T)
被调用。想想 Qt signal/slots 在 C++11 类固醇上。
我的下一个挑战是提供一个由子属性组成的类似 属性 的对象。例如,考虑一个位置或大小,它们都包含多个值。我希望能够将子对象视为 property
,并在一次更改多个值时另外获得一个组合信号。例如。做
struct
{
property<int> x;
property<int> y;
}
position2d pos{0,0};
// ...
pos = {1,1}; // this should fire x.value_changed, y.value_changed, and pos.value_changed (once!)
最后这个小词是问题的核心。我正在努力编写一个可以用子对象名称自定义的 可重用 composite_property
(位置会得到 x
,y
,但大小会得到width
/height
).
注意 property<struct { int x; int y; }>
是不够的:更改 x
不会发出复合 value_changed
信号。
我能想出的最好办法是在分配给超级对象时用一堆样板代码 connect/disconnect 子对象,这很乏味并且违反了 DRY 原则。
我对狂野的模板魔法持开放态度,尽管我理解变量的自由命名(x
/y
和 width
/height
)将使至少需要一些样板代码。
编辑 为了完整起见,这是我现在拥有的 property
模板:
template<typename T>
class property
{
public:
using value_type = T;
using reference = std::add_lvalue_reference_t<T>;
using const_reference = std::add_lvalue_reference_t<std::add_const_t<T>>;
using rvalue_reference = std::add_rvalue_reference_t<T>;
property(const_reference value_ = {}) : value(value_) {}
operator const_reference() const { return value; }
property& operator=(const_reference& other)
{
const bool changed = value != other;
value = other;
if(changed)
value_changed.emit(value);
return *this;
}
bool operator==(const_reference other) const { return value == other; }
bool operator!=(const_reference other) const { return value != other; }
bool operator< (const_reference other) const { return value < other; }
bool operator<=(const_reference other) const { return value <= other; }
bool operator> (const_reference other) const { return value > other; }
bool operator>=(const_reference other) const { return value >= other; }
signal<value_type> value_changed;
private:
value_type value;
};
signal
有点复杂,可用here。基本上,connect
和 Qt 一样,除了它 returns 一个像 Boost.Signal 的 connection_type
对象,它可以用于 disconnect
该连接。
请注意,我对绕过信号的后门 "modify property silently" 函数持开放态度,但它只实现了我需要的一半。
由于问题被标记为 c++1z,这里有一个简单的解决方案,它使用了一些闪亮的新 C++17 功能(沿着上面评论中讨论的内容):
template<class T, auto... PMs> struct composite_property : property<T>
{
using const_reference = typename property<T>::const_reference;
composite_property(const_reference v = {}) : property<T>(v)
{
(... , (this->value.*PMs).value_changed.connect([this](auto&&)
{
if(listen_to_members) this->value_changed.emit(this->value);
}));
}
composite_property& operator=(const_reference& other)
{
listen_to_members = false;
property<T>::operator=(other);
listen_to_members = true; // not exception-safe, should use RAII to reset
return *this;
}
private:
bool listen_to_members = true;
};
出于纯粹的懒惰,我对您的 property<T>
进行了更改:我已经 value
public。当然,有几种方法可以避免这种情况,但它们与手头的问题无关,所以我选择保持简单。
我们可以使用这个玩具示例测试解决方案:
struct position2d
{
property<int> x;
property<int> y;
position2d& operator=(const position2d& other)
{
x = other.x.value;
y = other.y.value;
return *this;
}
};
bool operator!=(const position2d& lhs, const position2d& rhs) { return lhs.x.value != rhs.x.value || lhs.y.value != rhs.y.value; }
int main()
{
composite_property<position2d, &position2d::x, &position2d::y> pos = position2d{0, 0};
pos.value.x.value_changed.connect([](int x) { std::cout << " x value changed to " << x << '\n'; });
pos.value.y.value_changed.connect([](int y) { std::cout << " y value changed to " << y << '\n'; });
pos.value_changed.connect([](auto&& p) { std::cout << " pos value changed to {" << p.x << ", " << p.y << "}\n"; });
std::cout << "changing x\n";
pos.value.x = 7;
std::cout << "changing y\n";
pos.value.y = 3;
std::cout << "changing pos\n";
pos = {3, 7};
}
这是一个 live example,其中包含所有必要的定义(我的代码在底部)。
虽然必须明确列出成员作为 composite_property
模板的参数可能很烦人,但它也提供了相当大的灵活性。例如,我们可以有一个具有三个成员属性的 class,并在不同的成员属性对上定义不同的复合属性。包含 class 不受任何影响,也可以独立于任何复合属性工作,成员用作独立属性。
请注意,position2d
的用户提供的复制赋值运算符是有原因的:如果我们将其保留为默认值,它将复制成员属性本身,这不会发出信号,而是复制源属性。
该代码适用于 C++1z 模式下的 Clang trunk。它还会在 GCC 主干上引起 ICE;我们应该尝试减少示例以获得可以在错误报告中提交的内容。
在这里发挥作用的关键 C++17 特性是 auto
非类型模板参数。在以前的语言版本中,有一些更丑陋的替代方案,例如,将指向成员的指针包装在 ptr_to_mem<decltype(&position2d::x), &position2d::x>
之类的东西中,可能使用宏来避免重复。
在 composite_property
的构造函数的实现中还有一个折叠表达式 ,
,但这也可以通过初始化一个虚拟数组来完成(以稍微更冗长的方式)。
我有一个简单的 property<T>
class 和 value_changed<T>
你可以 connect
/disconnect
接收或禁止事件 value_changed<T>::emit(T)
被调用。想想 Qt signal/slots 在 C++11 类固醇上。
我的下一个挑战是提供一个由子属性组成的类似 属性 的对象。例如,考虑一个位置或大小,它们都包含多个值。我希望能够将子对象视为 property
,并在一次更改多个值时另外获得一个组合信号。例如。做
struct
{
property<int> x;
property<int> y;
}
position2d pos{0,0};
// ...
pos = {1,1}; // this should fire x.value_changed, y.value_changed, and pos.value_changed (once!)
最后这个小词是问题的核心。我正在努力编写一个可以用子对象名称自定义的 可重用 composite_property
(位置会得到 x
,y
,但大小会得到width
/height
).
注意 property<struct { int x; int y; }>
是不够的:更改 x
不会发出复合 value_changed
信号。
我能想出的最好办法是在分配给超级对象时用一堆样板代码 connect/disconnect 子对象,这很乏味并且违反了 DRY 原则。
我对狂野的模板魔法持开放态度,尽管我理解变量的自由命名(x
/y
和 width
/height
)将使至少需要一些样板代码。
编辑 为了完整起见,这是我现在拥有的 property
模板:
template<typename T>
class property
{
public:
using value_type = T;
using reference = std::add_lvalue_reference_t<T>;
using const_reference = std::add_lvalue_reference_t<std::add_const_t<T>>;
using rvalue_reference = std::add_rvalue_reference_t<T>;
property(const_reference value_ = {}) : value(value_) {}
operator const_reference() const { return value; }
property& operator=(const_reference& other)
{
const bool changed = value != other;
value = other;
if(changed)
value_changed.emit(value);
return *this;
}
bool operator==(const_reference other) const { return value == other; }
bool operator!=(const_reference other) const { return value != other; }
bool operator< (const_reference other) const { return value < other; }
bool operator<=(const_reference other) const { return value <= other; }
bool operator> (const_reference other) const { return value > other; }
bool operator>=(const_reference other) const { return value >= other; }
signal<value_type> value_changed;
private:
value_type value;
};
signal
有点复杂,可用here。基本上,connect
和 Qt 一样,除了它 returns 一个像 Boost.Signal 的 connection_type
对象,它可以用于 disconnect
该连接。
请注意,我对绕过信号的后门 "modify property silently" 函数持开放态度,但它只实现了我需要的一半。
由于问题被标记为 c++1z,这里有一个简单的解决方案,它使用了一些闪亮的新 C++17 功能(沿着上面评论中讨论的内容):
template<class T, auto... PMs> struct composite_property : property<T>
{
using const_reference = typename property<T>::const_reference;
composite_property(const_reference v = {}) : property<T>(v)
{
(... , (this->value.*PMs).value_changed.connect([this](auto&&)
{
if(listen_to_members) this->value_changed.emit(this->value);
}));
}
composite_property& operator=(const_reference& other)
{
listen_to_members = false;
property<T>::operator=(other);
listen_to_members = true; // not exception-safe, should use RAII to reset
return *this;
}
private:
bool listen_to_members = true;
};
出于纯粹的懒惰,我对您的 property<T>
进行了更改:我已经 value
public。当然,有几种方法可以避免这种情况,但它们与手头的问题无关,所以我选择保持简单。
我们可以使用这个玩具示例测试解决方案:
struct position2d
{
property<int> x;
property<int> y;
position2d& operator=(const position2d& other)
{
x = other.x.value;
y = other.y.value;
return *this;
}
};
bool operator!=(const position2d& lhs, const position2d& rhs) { return lhs.x.value != rhs.x.value || lhs.y.value != rhs.y.value; }
int main()
{
composite_property<position2d, &position2d::x, &position2d::y> pos = position2d{0, 0};
pos.value.x.value_changed.connect([](int x) { std::cout << " x value changed to " << x << '\n'; });
pos.value.y.value_changed.connect([](int y) { std::cout << " y value changed to " << y << '\n'; });
pos.value_changed.connect([](auto&& p) { std::cout << " pos value changed to {" << p.x << ", " << p.y << "}\n"; });
std::cout << "changing x\n";
pos.value.x = 7;
std::cout << "changing y\n";
pos.value.y = 3;
std::cout << "changing pos\n";
pos = {3, 7};
}
这是一个 live example,其中包含所有必要的定义(我的代码在底部)。
虽然必须明确列出成员作为 composite_property
模板的参数可能很烦人,但它也提供了相当大的灵活性。例如,我们可以有一个具有三个成员属性的 class,并在不同的成员属性对上定义不同的复合属性。包含 class 不受任何影响,也可以独立于任何复合属性工作,成员用作独立属性。
请注意,position2d
的用户提供的复制赋值运算符是有原因的:如果我们将其保留为默认值,它将复制成员属性本身,这不会发出信号,而是复制源属性。
该代码适用于 C++1z 模式下的 Clang trunk。它还会在 GCC 主干上引起 ICE;我们应该尝试减少示例以获得可以在错误报告中提交的内容。
在这里发挥作用的关键 C++17 特性是 auto
非类型模板参数。在以前的语言版本中,有一些更丑陋的替代方案,例如,将指向成员的指针包装在 ptr_to_mem<decltype(&position2d::x), &position2d::x>
之类的东西中,可能使用宏来避免重复。
在 composite_property
的构造函数的实现中还有一个折叠表达式 ,
,但这也可以通过初始化一个虚拟数组来完成(以稍微更冗长的方式)。