模板参数循环依赖
Template Parameter Circular Dependency
我确实看到有人在这里问过这个问题:
C++: Template Parameter Cyclic Dependency
但是我不太明白那里接受的答案。
所以让我在这里重申一下这个问题。
假设我有:
template <class P>
class Consumer {
P m_producer;
public:
void consume(char* data, uint32_t length) {
if (/* some error condition */) {
m_producer.errorCallback();
}
}
}
template <class C>
class Producer {
C m_consumer;
void produce() {
char* someData;
uint32_t length;
m_consumer.consume(someData, length);
}
}
// Pseudocode -- not valid C++.
Producer<Consumer> c;
Consumer<Producer> p;
可以看到,Producer需要调用Consumer,Consumer需要调用Producer;两者作为模板参数相互依赖。
在不使用任何虚拟 class(接口)的情况下解析这种模板参数的最佳方法是什么?
提前致谢。
与任何此类概念性问题一样,答案是:重构、重构、重构。循环模板参数依赖表示您的业务逻辑中存在概念错误。
如果接受此依赖项,请考虑大致的内存布局:
// Not C++
Producer a {
m_consumer {
m_producer { // Not 'a', this is another producer.
m_consumer { // With an also different consumer inside.
// ... and it continues forever.
}
}
}
}
相反,让我们换个角度思考:如果我们只需要将一个消费者和一个生产者连接在一起,那么它们是同一数据结构的一部分才有意义。我们可以使用 Curiously Recurring Template Pattern 来避免生产者中的模板参数:
#include <iostream>
template<typename Producer>
class Consumer {
Producer& p;
public:
bool error{false};
Consumer(Producer& p_)
: p(p_)
{}
void consume(char* data, size_t lenght) {
if (!data) {
p.error_callback();
error = true;
}
}
};
class Producer : public Consumer<Producer>{
public:
Producer()
: Consumer<Producer>(*this)
{}
Consumer<Producer>& consumer() {return *this;}
void produce() {
char* some_data(nullptr);
int length{0};
consume(some_data, length);
}
void error_callback() {
std::cout << "Error\n";
}
};
int main() {
Producer producer;
auto& consumer = producer.consumer();
producer.produce();
if (consumer.error) {
std::cout << "Got error from consumer";
}
}
让我们考虑main()
前两行的内存布局:
// Not C++
Producer a {
consumer { // A producer implicitly has a consumer, since it inherits from it.
reference to a;
}
}
reference to consumer;
当然,这只是解决了你所说的问题。但这里真正的教训是:尝试考虑您期望的内存布局,这将帮助您弄清楚如何在代码中表达它。
我确实看到有人在这里问过这个问题: C++: Template Parameter Cyclic Dependency
但是我不太明白那里接受的答案。
所以让我在这里重申一下这个问题。
假设我有:
template <class P>
class Consumer {
P m_producer;
public:
void consume(char* data, uint32_t length) {
if (/* some error condition */) {
m_producer.errorCallback();
}
}
}
template <class C>
class Producer {
C m_consumer;
void produce() {
char* someData;
uint32_t length;
m_consumer.consume(someData, length);
}
}
// Pseudocode -- not valid C++.
Producer<Consumer> c;
Consumer<Producer> p;
可以看到,Producer需要调用Consumer,Consumer需要调用Producer;两者作为模板参数相互依赖。
在不使用任何虚拟 class(接口)的情况下解析这种模板参数的最佳方法是什么?
提前致谢。
与任何此类概念性问题一样,答案是:重构、重构、重构。循环模板参数依赖表示您的业务逻辑中存在概念错误。
如果接受此依赖项,请考虑大致的内存布局:
// Not C++
Producer a {
m_consumer {
m_producer { // Not 'a', this is another producer.
m_consumer { // With an also different consumer inside.
// ... and it continues forever.
}
}
}
}
相反,让我们换个角度思考:如果我们只需要将一个消费者和一个生产者连接在一起,那么它们是同一数据结构的一部分才有意义。我们可以使用 Curiously Recurring Template Pattern 来避免生产者中的模板参数:
#include <iostream>
template<typename Producer>
class Consumer {
Producer& p;
public:
bool error{false};
Consumer(Producer& p_)
: p(p_)
{}
void consume(char* data, size_t lenght) {
if (!data) {
p.error_callback();
error = true;
}
}
};
class Producer : public Consumer<Producer>{
public:
Producer()
: Consumer<Producer>(*this)
{}
Consumer<Producer>& consumer() {return *this;}
void produce() {
char* some_data(nullptr);
int length{0};
consume(some_data, length);
}
void error_callback() {
std::cout << "Error\n";
}
};
int main() {
Producer producer;
auto& consumer = producer.consumer();
producer.produce();
if (consumer.error) {
std::cout << "Got error from consumer";
}
}
让我们考虑main()
前两行的内存布局:
// Not C++
Producer a {
consumer { // A producer implicitly has a consumer, since it inherits from it.
reference to a;
}
}
reference to consumer;
当然,这只是解决了你所说的问题。但这里真正的教训是:尝试考虑您期望的内存布局,这将帮助您弄清楚如何在代码中表达它。