根据运行时值

时间:2016-10-04 01:24:16

标签: c++ templates c++11

这与previous question相关,因为它是同一系统的一部分,但它是一个不同的问题。

我正在开发一个内部消息传递系统,该系统旨在向消费者发送消息(struct)。

当项目想要使用消息传递系统时,它将定义一组消息(enum class),数据类型(struct)以及这些实体之间的关系:

template <MessageType E> struct expected_type;
template <> struct expected_type<MessageType::TypeA> { using type = Foo; };
template <> struct expected_type<MessageType::TypeB> { using type = Bar; };
template <> struct expected_type<MessageType::TypeM> { using type = Foo; };

请注意,不同类型的消息可能使用相同的数据类型。

我之前的问题讨论了发送这些消息的代码。有一个模板化方法可以发送任何消息,并使用上面的模板定义维护类型安全。它工作得非常好。

我的问题是关于消息接收器类。有一个基类,它实现了这样的方法:

ReceiveMessageTypeA(const Foo & data) { /* Some default action */ };
ReceiveMessageTypeB(const Bar & data) { /* Some default action */ };
ReceiveMessageTypeM(const Foo & data) { /* Some default action */ };

然后实现单个消息处理功能,如下所示:

bool ProcessMessage(MessageType msgType, void * data) {
  switch (msgType) {
    case TypeA:
      ReceiveMessageTypeA(data);
      break;

    case TypeB:
      ReceiveMessageTypeB(data);
      break;

    // Repeat for all supported message types

    default:
      // error handling
      break;
  }
}

当需要消息接收器时,将扩展此基类,并实现所需的ReceiveMessageTypeX方法。如果该特定接收者不关心消息类型,则相应的函数未被实现,而是使用基类的默认值。

旁注:忽略我传递void *而不是特定类型的事实。中间还有一些代码来处理所有这些,但它不是相关细节。

该方法的问题是添加了新的消息类型。除了必须定义enumstructexpected_type<>特化之外,还必须修改基类以添加新的ReceiveMessageTypeX默认方法和switch语句必须更新ProcessMessage函数。

我想避免手动修改基类。具体来说,我希望使用expected_type中存储的信息来完成繁重的工作,并避免重复。

这是我尝试过的解决方案:

在基类中,定义一个方法:

template <MessageType msgType>
bool Receive(expected_type<msgType>::type data) { 
    // Default implementation. Print "Message not supported", or something
}

然后,子类可以实现他们关心的特化:

template<> Receive<MessageType::TypeA>(const Foo & data) { /* Some processing */ }
// Don't care about TypeB
template<> Receive<MessageType::TypeM>(const Foo & data) { /* Some processing */ }

我认为这解决了部分问题;我不需要在基类中定义新方法。

但我无法弄清楚如何摆脱switch语句。我希望能够做到这一点:

bool ProcessMessage(MessageType msgType, void * data) {
  Receive<msgType>(data);
}

当然,这不会做,因为模板不会像那样工作。

我想到的事情:

  1. expected_type结构生成switch语句。我不知道该怎么做。
  2. 维护某种函数指针映射,并调用所需的函数指针。问题是我不知道如何初始化地图而不重复来自expected_type的数据,这是我不想做的。
  3. 使用宏定义expected_type,然后播放预处理器游戏以将该数据按到switch语句中。这可能是可行的,但我尽可能避免使用宏。
  4. 因此,总而言之,我希望能够基于运行时值调用不同的模板专门化。这似乎与我相矛盾,但我希望有人可以指出我有用的方向。即使这告诉我这不是一个好主意。

    如果需要,我可以更改expected_type,只要它不会破坏我的Send方法(请参阅我的other question)。

2 个答案:

答案 0 :(得分:1)

您对collection := session.DB("your_db_name").C("main") err = collection.Remove(bson.M{"id":1}) expected_type模板有正确的想法;只剩下一步才能完成所有工作。

首先,我们需要给我们一些枚举MessageType的方法:

Receive

然后我们可以在编译时枚举MessageType并生成调度函数(使用SFINAE跳过未在enum class MessageType { _FIRST = 0, TypeA = _FIRST, TypeB, TypeM = 100, _LAST }; 中定义的值):

expected_types

答案 1 :(得分:0)

您的标题是:&#34;根据运行时值调用不同的模板函数特化&#34;

只能通过某种手动switch声明或virtual函数来完成。

一方面,它看起来就像你正在进行面向对象的编程,但你还没有任何virtual方法。如果你发现你到处都在写伪对象,但你没有任何virtual函数,那么这意味着你没有做OOP。这不是一件坏事。如果你过度使用OOP,那么你可能无法理解它有用的特殊情况,因此它只会引起更多的混淆。

简化您的代码,不要被OOP分心

您希望消息类型对象具有一些魔法&#39;与之关联,它MessageType控制它的分派方式。这意味着您需要一个虚拟功能。

 struct message {
     virtual void Receive() = 0;
 }

 struct message_type_A : public message {
      virtual void Receive() {
          ....
      }
 }

这允许您在适当的情况下将这些对象作为message&传递,并致电msg.process_me()