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

时间:2017-10-30 09:24:18

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

我有一个工作分配计划,每个单位都会进行一些簿记和管理,并将任务传递给下一个订单链,例如;让我们说有3个班:老板,经理,工人

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函数是否可能/更可取(作为性能或代码质量)?

编辑:为什么我需要在每个级别进行异步访问,而不仅仅是第一级?因为每个类之间存在多对多的关系。我有几层处理单元(Boss或Manager或Worker),可以订购下一层中的任何人。当一个单位将作业命令到下一行。必须立即免费从上面接受新订单。

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

1 个答案:

答案 0 :(得分:3)

你在这里做的是管道数据。拥抱管道。

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();

和工人和老板类似。

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 );
    });
  };
}

现在,我创建了源和#34;接收器用于接收器&#34;,因为它允许源(或管道)从一次调用生成1,0或许多消息。我觉得这在很多情况下很有用,但它确实使编写管道有点奇怪。

您也可以在中完整地编写此内容,而不必使用std::function,只需应用&#34;我就是一个接收器&#34;和&#34;我是一个来源&#34;和&#34;我是一个管道&#34;标签为lambdas(通过组合,如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 - 更少版本:

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 typedef非常有用。

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