简体   繁体   English

C++ 回调中生产者和消费者之间的循环模板依赖

[英]C++ circular template dependency between Producer and Consumer in callback

I am designing two classes which encapsulate a producer and consumer.我正在设计两个封装生产者和消费者的类。 One class does single-thread processing, the other (not shown here) two threads.一个 class 进行单线程处理,另一个(此处未显示)两个线程。

The producer and consumer are passed to each 'threading class' by template parameters.生产者和消费者通过模板参数传递给每个“线程类”。

*this is passed to the producer to enable a callback upon each message: *this是传递给生产者以启用对每条消息的回调:

template<class PRODUCER, class CONSUMER>
class SingleThread
{
    SingleThread() : _prod(*this){}

    void processMessage(const char* message, int len)
    {
        int d = _cons.doSomething(message, len);
        // Code omitted
    }

    PRODUCER<SingleThread<>> _prod;  //  Cyclical dependency
    CONSUMER _cons;
};

Each producer receives the threading class as a callback via template parameter:每个生产者通过模板参数接收线程 class 作为回调:

template<class THREADING_CALLBACK>
class Producer
{
    Producer(THREADING_CALLBACK& cb) : _cb(cb){}

    // A lot of code omitted

    void receiveMessage(const char* message, int len)
    {
        _cb.processMessage(message, len);
    }

    THREADING_CALLBACK& _cb;
};

but I have a problem with circular template dependency.但我有循环模板依赖的问题。 SingleThread takes Producer as a template parameter but Producer requires SingleThread : SingleThreadProducer作为模板参数,但Producer需要SingleThread

SingleThread<Producer<SingleThread<Producer<....>, Consumer>, Consumer>

How should I (re)structure this design?我应该如何(重新)构建这个设计?

You can omit the template arguments:您可以省略模板 arguments:

template<class THREADING_CALLBACK>
class Producer
{
public:
    Producer(THREADING_CALLBACK& cb) : _cb(cb){}

    void receiveMessage(const char* message, int len)
    {
        _cb.processMessage(message, len);
    }

    THREADING_CALLBACK& _cb;
};

template<template <class> class PRODUCER, class CONSUMER>
class SingleThread
{
public:
    SingleThread() : _prod(*this){}

    void processMessage(const char* message, int len)
    {
        // Code omitted
    }

    PRODUCER<SingleThread> _prod;
    CONSUMER _cons;
};

You probably dont have requirement that producer knows about callback more than it looks like functor.您可能没有要求生产者比函子更了解回调。

using THREADING_CALLBACK = std::function<void(const char* message, int len)>;

This way, Producer does not need to take THREADING_CALLBACK as template parameter.这样,Producer 就不需要将 THREADING_CALLBACK 作为模板参数。

class Producer
{
  public:
  Producer(THREADING_CALLBACK& cb) : _cb(cb){}

  void receiveMessage(const char* message, int len)
  {
    _cb(message, len);
  }

  THREADING_CALLBACK _cb;
};

As a framing challenge, as written the producer has no business knowing the implementation of the threading callback.作为一个框架挑战,正如所写的那样,生产者不知道线程回调的实现。

The callback is just a sink for some data.回调只是一些数据的接收器。

Second, make it a value-type rather than a reference;其次,使其成为值类型而不是引用; it can internally do reference semantics if it likes.如果愿意,它可以在内部进行引用语义。

template<class DATA_SINK>
class Producer
{
public:
    Producer(DATA_SINK cb) : _cb(cb){}

    void receiveMessage(const char* message, int len)
    {
        _cb(message, len);
    }

    DATA_SINK _cb;
};

we now see that the interface of DATA_SINK is void(C-style string, length) , aka std::string_view .我们现在看到DATA_SINK的接口是void(C-style string, length) ,也就是std::string_view

    void receiveMessage(std::string_view msg)
    {
        _cb(msg);
    }

now, the question is, how much overhead is there in an indirect call here, compared to your workloads?现在,问题是,与您的工作负载相比,这里的间接调用有多少开销?

class Producer
{
public:
    Producer(std::function<void(std::string_view)> cb) : _cb(cb){}

    void receiveMessage(const char* message, int len)
    {
        _cb(message, len);
    }

    std::function<void(std::string_view)> _cb;
};

we now have decoupled the Producer from what consumes its data.我们现在已经将Producer与使用其数据的对象分离。 SingleThread , because it owns the producer, still gets the template argumetn. SingleThread ,因为它拥有生产者,仍然获得模板参数。

template<class PRODUCER, class CONSUMER>
class SingleThread
{
    SingleThread() : _prod([this](std::string_view sv){
      processMessage(sv);
    }){}

    void processMessage(std::string_view sv)
    {
        int d = _cons.doSomething(sv);
        // Code omitted
    }

    PRODUCER _prod;
    CONSUMER _cons;
};

dependency is now removed.现在删除了依赖项。 We can continue to tear apart dependencies in a similar way if we want.如果需要,我们可以继续以类似的方式分离依赖项。

You'll see above that I embedded the "pointer semantics" of the data-sink in the producer into the callback I installed;您将在上面看到,我将生产者中数据接收器的“指针语义”嵌入到我安装的回调中;

    [this](std::string_view sv){
      processMessage(sv);
    }

here I store a pointer to the state of the callback, while Producer stores an actual value.这里我存储了一个指向回调的 state 的指针,而Producer存储了一个实际值。

A side benefit to this strategy is that it produces a nice point where we can mock the API that Producer communicates with.这种策略的另一个好处是它产生了一个很好的点,我们可以模拟Producer与之通信的 API。

The cost of bouncing through a std::function is not zero, but it isn't high unless you are doing something like calling it for every pixel on a HD image at 60 FPS.通过std::function弹跳的成本不是零,但它并不高,除非你正在做一些事情,比如以 60 FPS 的速度为高清图像上的每个像素调用它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM