[英]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
: SingleThread
将Producer
作为模板参数,但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.