9

在 OO 中,通常使用接口实现回调:(粗略示例)

class Message {}

class IMsgProcessor {
public:
     virtual void handle_msg(const Message& msg) = 0;
}

class RequestMsgProcessor : public IMsgProcessor {
     virtual void handle_msg(const Message& msg)  {
     // process request message
    }
}

class CustomSocket {
public:
   Socket(IMsgProcessor* p) : processor_(p) {}

   void receive_message_from_network(const Message& msg) {
       // processor_ does implement handle_msg. Otherwise a compile time error. 
       // So we've got a safe design.
       processor_->handle_msg(msg);
   }
private:
   IMsgProcessor* processor_;
}

到现在为止还挺好。使用 C++11,另一种方法是让 CustomSocket 只接收 std::function 对象的实例。它不关心它是在哪里实现的,也不关心对象是否为非空值:

class CustomSocket {
public:
   Socket(std::function<void(const Message&)>&& f) : func_(std:forward(f)) {}

   void receive_message_from_network(const Message& msg) {
       // unfortunately we have to do this check for every msg.
       // or maybe not ...
       if(func_)
            func_(msg);
   }
private:
   std::function<void(const Message&)> func_;
}

现在有以下问题:
1. 性能影响如何?我猜虚拟函数调用比调用函数对象要快,但要快多少?我正在实现一个快速的消息传递系统,我宁愿避免任何不必要的性能损失。
2. 在软件工程实践方面,我不得不说我更喜欢第二种方法。更少的代码,更少的文件,更少的混乱:没有接口类。更大的灵活性:您只能通过设置一些函数对象而将其他函数对象保留为空来实现接口的子集。或者,您可以在单独的类中或通过自由函数或两者的组合(而不是在单个子类中)实现接口的不同部分。此外,任何类都可以使用 CustomSocket,而不仅仅是 IMsgProcessor 的子类。这是一个很大的优势,在我看来。
你说什么?你认为这些论点有什么根本缺陷吗?

4

3 回答 3

3

你可以两全其美

template<class F>
class MsgProcessorT:public IMsgProcessor{
  F f_;
  public:
  MsgProcessorT(F f):f_(f){}
  virtual void handle_msg(const Message& msg)  {
      f_(msg);
 }

};
template<class F>
IMsgProcessor* CreateMessageProcessor(F f){
    return new MsgProcessor<T>(f);

};

然后你可以像这样使用

Socket s(CreateMessageProcessor([](const Message& msg){...}));

或者为了更容易将另一个构造函数添加到 Socket

class Socket{
...
template<class F>
Socket(F f):processor_(CreateMessageProcessor(f){}


};

然后你可以做

Socket s([](const Message& msg){...});

并且仍然具有与虚函数调用相同的效率

于 2012-11-29T15:06:18.677 回答
2

接口方法更传统但也更冗长:很清楚 a做了什么MessageProcessor,您不必检查它。此外,您可以通过多个 Socket 重复使用同一个对象。

该方法更通用:可以使用std::function任何接受的东西。operator()(Message const&)然而,缺乏冗长有降低代码可读性的危险。

我不知道性能损失,但如果存在显着差异,我会感到惊讶。

如果这是您的代码的重要部分(看起来是这样),我会坚持使用接口方法。

于 2012-11-29T14:56:14.733 回答
1

在您的两个示例中,您实际上都在使用接口。不同的是你定义它们的方式。在第一种情况下,接口是具有纯虚函数的传统类,而在第二种情况下,接口是函数引用 - 从设计角度来看,它与 C 函数指针有很大不同。在我看来,您可以根据具体要求混合两种变体,并考虑每个新案例的利弊(就像您所说的那样)。关于性能影响,我认为最好的答案是执行测试、比较结果并匹配您的性能要求。

于 2012-11-29T14:57:27.127 回答