c++ - 许多嵌套回调的优点/缺点?

标签 c++ stl c++14 c++17

我有一个工作分配方案,其中每个单元都进行一些簿记和管理,并将任务传递给订单链中的下一个单元,例如;假设有 3 个类:老板、经理、 worker

class Boss
{
    void do_async(Request req, std::function<void(Result)> callback)
    {
        //Find eligible manager etc.
        manager.do_async(Boss_request(req,...),std::bind(&Boss::callback,this,callback,std::placeholders::_1));

    }
    void callback(std::function<void(Result)> main_callback,Boss_result res)
    {
        //some book keeping
        main_callback(res.main_part);
    }
};

class Manager
{
    void do_async(Boss_request req, std::function<void(Boss_result)> boss_callback)
    {
        //Find eligible worker etc. add some data to request
        worker.do_async(Manager_request(req,...),std::bind(&Manager::callback,this,boss_callback,std::placeholders::_1));
    }

    void callback(std::function<void(Boss_result)> boss_callback,Manager_result res)
    {
        //some book keeping
        boss_callback(res.boss_part);
    }
};

class Worker
{
    void do_async(Manager_request req, std::function<void(Manager_result)> manager_callback)
    {
        //Do job async
        work_async(Worker_task(req,...),std::bind(&Worker::callback,this,manager_callback,std::placeholders::_1));
    }

    void callback(std::function<void(Manager_result)> manager_callback,Worker_result res)
    {
        //some book keeping
        manager_callback(res.manager_part);
    }
};

如您所见,我广泛使用了 std::bind、std::function 和 std::placeholder。这种方法有什么优点/缺点吗?如果不是可取的,那么更好的方法是什么?在此状态下使用 lambda 函数是否可能/更可取(作为性能或代码质量)?

编辑:为什么我需要在每个级别进行异步访问,而不仅仅是第一级?因为每个类之间存在多对多关系。我有几层处理单元(老板或经理或 worker )可以订购下一层的任何人。当一个单位将工作交给下一个单位时。它必须立即免费接受来自上面的新订单。

我没有直接使用 lambda,因为回调可能有点大,可能会使其更难阅读。但是,如果存在任何显着的性能损失,则可能会牺牲代码质量。

最佳答案

您在这里所做的是传输数据。拥抱管道。

namespace chain {
  template<class T, class Base=std::function<void(T)>>
  struct sink:Base{
    using Base::operator();
    using Base::Base;
  };

  template<class T, class F>
  sink<T> make_sink( F&& f ) {
    return {std::forward<F>(f)};
  }

  template<class T>
  using source=sink<sink<T>>;

  template<class T, class F>
  source<T> make_source( F&& f ) {
    return {std::forward<F>(f)};
  }
  template<class T>
  source<std::decay_t<T>> simple_source( T&& t ) {
    return [t=std::forward<T>(t)]( auto&& sink ) {
      return sink( t );
    };
  }
  template<class In, class Out>
  using pipe = std::function< void(source<In>, sink<Out>) >;

  template<class In, class Out>
  sink<In> operator|( pipe<In, Out> p, sink<Out> s ) {
    return [p,s]( In in ) {
      p( [&]( auto&& sink ){ sink(std::forward<In>(in)); }, s );
    };
  }
  template<class In, class Out>
  source<Out> operator|( source<In> s, pipe<Out> p ) {
    return [s,p]( auto&& sink ) {
      p( s, decltype(sink)(sink) );
    };
  }
  template<class T>
  std::function<void()> operator|( source<T> in, sink<T> out ) {
    return [in, out]{ in(out); };
  }
  template<class In, class Mid, class Out>
  pipe<In, Out> operator|( pipe<In, Mid> a, pipe<Mid, Out> b ) {
    return [a,b]( source<In> src, sink<Out> dest ) {
      b( src|a, dest  );
      // or a( src, b|dest );
      // but I find pipe|sink -> sink to be less pleasing an implementation
    };
  }
}//namespace

然后写这些:

pipe<Request, Result> Boss::work_pipe();

pipe<Boss_request, Boss_result> Manager::work_pipe();
pipe<Boss_request, Manager_request> Manager::process_request();
pipe<Manager_request, Manager_result> Manager::do_request();
pipe<Manager_result, Boss_results> Manager::format_result();

pipe<Manager_request, Manager_result> Worker::work_pipe();

对于 Worker 和 Boss 也是类似的。

pipe<Request, Result> Boss::work_pipe() {
  return process_request() | do_request() | format_result();
}
pipe<Boss_request, Boss_result> Manager::work_pipe() {
  return process_request() | do_request() | format_result();
}
pipe<Manager_request, Manager_result> Worker::work_pipe() {
  return process_request() | do_request() | format_result();
}

然后:

pipe<Manager_request, Manager_result> Manager::do_request() {
  return [this]( source<Manager_request> src, sink<Manager_result> dest ) {
    // find worker
    worker.do_request( src, dest );
  };
}
pipe<Manager_output, Boss_result> Manager::format_result() {
  return [this]( source<Manager_output> src, sink<Boss_result> dest ) {
    src([&]( Manager_output from_worker ) {
      // some book keeping
      dest( from_worker.boss_part );
    });
  };
}

现在,我将源设为“汇汇汇”,因为它允许源(或管道)从一次调用中生成 1、0 或许多消息。我发现这在很多情况下都很有用,但它确实让编写管道变得有点陌生。

也可以写在中根本不使用 std::function,只需将“我是一个接收器”、“我是一个源”和“我是一个管道”标签应用于 lambda(通过组合,如 override) 然后盲目地用 | 连接起来并希望它们的类型兼容。

do_sync,你只需这样做:

void Boss::do_async( Request req, sink<Result> r ) {
  work_async( simple_source(req) | work_pipe() | r );
}

也就是说,整个计算可以捆绑起来并四处移动。这会将线程工作移至顶部。

如果您需要异步线程实现在底部,您可以将前面的工作管道化并向下传递。

void Boss::do_async( source<Request> req, sink<Result> r ) {
  find_manager().do_async( req|process_request(), format_result()|r );
}
void Manager::do_async( source<Boss_request> req, sink<Boss_result> r ) {
  find_worker().do_async( req|process_request(), format_result()|r );
}
void Worker::do_async( source<Manager_request> req, sink<Manager_result> r ) {
  work_async( req|process_request()|do_request()|format_result()|r );
}

由于水槽/源头/管道的组成方式,您可以选择将组成的哪些部分向下传递以及向上传递哪些部分。

std::function-less 版本:

namespace chain {
  struct pipe_tag{};
  struct sink_tag{};
  struct source_tag{};

  template<class T, class=void>
  struct is_source:std::is_base_of<source_tag, T>{};
  template<class T, class=void>
  struct is_sink:std::is_base_of<sink_tag, T>{};
  template<class T, class=void>
  struct is_pipe:std::is_base_of<pipe_tag, T>{};

  template<class F, class Tag>
  struct tagged_func_t: F, Tag {
    using F::operator();
    using F::F;
    tagged_func_t(F&& f):F(std::move(f)) {}
  };
  template<class R, class...Args, class Tag>
  struct tagged_func_t<R(*)(Args...), Tag>: Tag {
    using fptr = R(*)(Args...);
    fptr f;
    R operator()(Args...args)const{
      return f( std::forward<Args>(args)... );
    }
    tagged_func_t(fptr fin):f(fin) {}
  };

  template<class Tag, class F>
  tagged_func_t< std::decay_t<F>, Tag >
  tag_func( F&& f ) { return {std::forward<F>(f)}; }

  template<class F>
  auto as_pipe( F&& f ) { return tag_func<pipe_tag>(std::forward<F>(f)); }
  template<class F>
  auto as_sink( F&& f ) { return tag_func<sink_tag>(std::forward<F>(f)); }
  template<class F>
  auto as_source( F&& f ) { return tag_func<source_tag>(std::forward<F>(f)); }

  template<class T>
  auto simple_source( T&& t ) {
    return as_source([t=std::forward<T>(t)]( auto&& sink ) {
      return sink( t );
    });
  }

  template<class Pipe, class Sink,
    std::enable_if_t< is_pipe<Pipe>{} && is_sink<Sink>{}, bool> = true
  >
  auto operator|( Pipe p, Sink s ) {
    return as_sink([p,s]( auto&& in ) {
      p( [&]( auto&& sink ){ sink(decltype(in)(in)); }, s );
    });
  }
  template<class Source, class Pipe,
    std::enable_if_t< is_pipe<Pipe>{} && is_source<Source>{}, bool> = true
  >
  auto operator|( Source s, Pipe p ) {
    return as_source([s,p]( auto&& sink ) {
      p( s, decltype(sink)(sink) );
    });
  }
  template<class Source, class Sink,
    std::enable_if_t< is_sink<Sink>{} && is_source<Source>{}, bool> = true
  >
  auto operator|( Source in, Sink out ) {
    return [in, out]{ in(out); };
  }
  template<class PipeA, class PipeB,
    std::enable_if_t< is_pipe<PipeA>{} && is_pipe<PipeB>{}, bool> = true
  >
  auto operator|( PipeA a, PipeB b ) {
    return as_pipe([a,b]( auto&& src, auto&& dest ) {
      b( src|a, dest  );
      // or a( src, b|dest );
      // but I find pipe|sink -> sink to be less pleasing an implementation
    });
  }

  template<class T>
  using sink_t = tagged_func_t< std::function<void(T)>, sink_tag >;
  template<class T>
  using source_t = tagged_func_t< std::function<void(sink_t<T>)>, source_tag >;
  template<class In, class Out>
  using pipe_t = tagged_func_t< std::function<void(source_t<In>, sink_t<Out>)>, pipe_tag >;
}

类型检查较少,但消除了类型删除开销。

sink_tsource_tpipe_t 类型定义在您需要对它们进行类型删除时很有用。

"Hello world" example使用非类型删除版本。

关于c++ - 许多嵌套回调的优点/缺点?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47011725/

相关文章:

javascript - 如果无法分配内存,V8 会崩溃吗?这会使整个过程崩溃吗?

c++ - 如何在 c++11/c++14 中定义一个以枚举为键、以整数为值的映射?

c++ - 在std::exchange中,为什么第二个模板参数默认了?

c++ - 按名称调用不同签名的方法

c++ - 使用 ldap_get_next_page_s 进行 LDAP 排序会导致错误 LDAP_UNAVAILABLE_CRIT_EXTENSION

c++ - 更快的阅读和更快的处理

c++ - 为什么 C++ 中的分号在代码开头插入时不显示任何错误?

c++ - 在 C++(98、11 和 14)中初始化静态数据成员的正确方法是什么

STL - STL count_if 的标准谓词

c++ - 什么时候在 C++ 中使用函数对象?