草庐IT

c++ - 当所有子对象都改变时抑制多个信号发射的复合属性对象

coder 2023-11-14 原文

我有一个简单的 property<T>value_changed<T>你可以connect/disconnectvalue_changed<T>::emit(T) 时接收或禁止事件叫做。想想 C++11 类固醇上的 Qt 信号/槽。

我的下一个挑战是提供一个由子属性组成的类似属性的对象。例如,考虑一个位置或大小,它们都包含多个值。我希望能够将子对象视为 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可以使用子对象名称自定义(位置会得到 xy ,但大小会得到 width/height )。

注意一个property<struct { int x; int y; }>不够:更改 x不会发出复合 value_changed信号。

我能想出的最好办法是用一堆样板代码在分配给 super 对象时连接/断开子对象,这很乏味并且违反了 DRY 原则。

尽管我理解变量的自由命名(x/ywidth/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,只是它返回一个 connection_type像 Boost.Signal 这样的对象,可用于 disconnect这种联系。

请注意,我对绕过信号的后门“静默修改属性”功能持开放态度,但它只实现了我需要的一半。

最佳答案

由于问题被标记为 c++1z ,这是一个简单的解决方案,它使用了一些 Shiny 的新 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上市。当然,有几种方法可以避免这种情况,但它们与手头的问题无关,所以我选择保持简单。

我们可以使用这个玩具示例测试解决方案:

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 的参数模板可能很烦人,但它也提供了相当大的灵 active 。例如,我们可以有一个具有三个成员属性的类,并在不同的成员属性对上定义不同的复合属性。包含类不受任何影响,也可以独立于任何复合属性工作,成员用作独立属性。

请注意,用户提供的复制赋值运算符 position2d 是有原因的: 如果我们保留它的默认值,它会复制成员属性本身,这不会发出信号,而是复制源属性。

该代码适用于 C++1z 模式下的 Clang 主干。它还会在 GCC 主干上引起 ICE;我们应该尝试减少示例以获得可以在错误报告中提交的内容。

这里起作用的关键 C++17 特性是 auto非类型模板参数。在以前的语言版本中,有一些更丑陋的替代方案,例如,将指向成员的指针包装在类似 ptr_to_mem<decltype(&position2d::x), &position2d::x> 的内容中,可能使用宏来避免重复。

, 上还有一个折叠表达式在 composite_property 的构造函数的实现中,但这也可以通过初始化一个虚拟数组来完成(以稍微更冗长的方式)。

关于c++ - 当所有子对象都改变时抑制多个信号发射的复合属性对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41003823/

有关c++ - 当所有子对象都改变时抑制多个信号发射的复合属性对象的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  3. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  4. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

    我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

  5. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  6. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  7. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  8. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  9. ruby - 多个属性的 update_column 方法 - 2

    我有一个具有一些属性的模型:attr1、attr2和attr3。我需要在不执行回调和验证的情况下更新此属性。我找到了update_column方法,但我想同时更新三个属性。我需要这样的东西:update_columns({attr1:val1,attr2:val2,attr3:val3})代替update_column(attr1,val1)update_column(attr2,val2)update_column(attr3,val3) 最佳答案 您可以使用update_columns(attr1:val1,attr2:val2

  10. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

随机推荐