使用接口或函数对象进行回调?

时间:2012-11-29 14:42:04

标签: c++ design-patterns interface c++11

在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的子类。在我看来,这是一个<强大的优势。
你说什么?你认为这些论点存在任何根本缺陷吗?

3 个答案:

答案 0 :(得分: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){...});

仍具有与虚拟函数调用相同的效率

答案 1 :(得分:2)

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

std::function方法更为通用:可以使用接受operator()(Message const&)的任何内容。但是,缺乏冗长会导致代码可读性降低。

我不了解绩效惩罚,但如果存在重大差异,会感到惊讶。

如果这是代码的一个重要部分(我们似乎是这样),我会坚持使用接口方法。

答案 2 :(得分:1)

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