回调的优缺点(std :: function/std :: bind)与接口(抽象类)

sim*_*mon 34 c++ interface callback boost-asio c++11

我正在使用Boost.Asio在C++ 11中创建服务器应用程序.我创建了一个类,Server它负责接受新的连接.它基本上只是:

void Server::Accept() {
  socket_.reset(new boost::asio::ip::tcp::socket(*io_service_));
  acceptor_.async_accept(*socket_,
                         boost::bind(&Server::HandleAccept, this, boost::asio::placeholders::error));
}

void Server::HandleAccept(const boost::system::error_code& error) {
  if (!error) {
    // TODO
  } else {
    TRACE_ERROR("Server::HandleAccept: Error!");
  }
  Accept();
}
Run Code Online (Sandbox Code Playgroud)

我找到了两种方法(我确定还有更多)来"修复" TODO注释,即将套接字移动到应该去的地方.在我的情况下,我只想将它返回到拥有该Server实例的类实例(然后将其包装在一个Connection类中并将其插入到列表中).

  1. Server在其构造函数中有一个参数:std::function<void(socket)> OnAccept调用它HandleAccept.
  2. 我创建了一个抽象类,IServerHandler或者其他什么,它有一个虚方法OnAccept.Server需要IServerHandler在其构造函数的参数和类拥有实例的服务器实例扩展IServerHandler,构建Server*this作为参数.

选项1与选项2的优缺点是什么?还有更好的选择吗?我的Connection班级(OnConnectionClosed)中遇到了同样的问题.此外,根据我决定如何设计系统,它可能需要一个OnPacketReceivedOnPacketSent回调.

Man*_*726 45

我非常喜欢第一种方式,原因如下:

  • 通过接口/类层次结构表示概念/功能使得代码库不那么通用,灵活,并且在将来更难以保持或扩展.这种设计对类型(实现所需功能的类型)施加了一系列要求,这使得将来很难进行修改,并且在系统更改时最容易失败(考虑在修改基类时会发生什么)这种类型的设计).

  • 你所谓的回调方法只是鸭子打字的经典例子.服务器类只需要一个可调用的东西来实现所需的功能,仅此而已.不需要"您的类型必须耦合到此层次结构"条件,因此实现处理的类型是完全免费的.

  • 另外,正如我所说,服务器只需要一个可调用的东西:它可以是具有预期功能签名的任何东西.这为用户在实现处理程序时提供了更大的自由度.可以是全局函数,绑定成员函数,函子等.

以标准库为例:

  • 几乎所有标准库算法都基于迭代器范围.C++中没有iterator接口.迭代器只是实现迭代器行为的任何类型(可解除引用,可比较等).迭代器类型是完全自由的,不同的和解耦的(未锁定到给定的类层次结构).

  • 另一个例子可能是比较器:比较器是什么?只是具有布尔比较函数签名的任何东西,可调用的东西,它接受两个参数并返回一个布尔值,表示从特定比较条件的角度看两个输入值是否相等(小于,大于等) .没有Comparable界面.

  • 硬币有两面,这个答案侧重于*可调*方法的优点.另一方面,使用仅回调的复杂系统更难以推理和维护,因为程序的执行流程从代码中看不清楚(有些东西被调用,去看看是什么).每种方法都有其优点,并且在复杂系统中占有一席之地. (7认同)