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



我正在使用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();
}

我找到了两种方法(我相信还有更多(来"修复"TODO注释,即将套接字移动到它应该去的地方。在我的情况下,我只希望它返回到拥有Server实例的类实例(然后将它封装在Connection类中并插入到列表中(。

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

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

我非常喜欢第一种方式,原因有几个:

  • 通过接口/类层次结构表示概念/功能会使代码库变得不那么通用、灵活,而且在未来更难维护或扩展。这种设计对类型(实现所需功能的类型(强加了一组要求,这使得将来很难修改,并且在系统更改时最容易失败(考虑在这种类型的设计中修改基类时会发生什么(。

  • 您所称的回调方法只是鸭子类型的经典示例。服务器类只需要一个可调用的东西来实现所需的功能,没有更多,也没有更少。不需要"您的类型必须耦合到此层次结构">条件,因此实现处理的类型是完全免费的

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

以标准库为例:

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

  • 另一个例子可能是比较器:什么是比较器就是任何带有布尔比较函数签名的东西,它是可调用的,它接受两个参数,并返回一个布尔值,从特定比较标准的角度来看,如果两个输入值相等(小于、大于等(没有Comparable接口

您使用的是什么版本的boost?IMHO的最佳方式是使用协同程序。代码将继续。它看起来像同步代码,但现在我无法进行比较,因为我是在移动设备上写的。

仅需提及,在许多情况下,您会将PREFER绑定到特定类型
因此,在这种情况下,声明您的类必须具有IServerHandler可以帮助您和其他开发人员了解他们应该实现什么接口才能使用您的类
在未来的开发中,当您向IServerHandler添加更多功能时,您会迫使您的客户端(即派生类(跟上您的开发
这可能是所需的行为。

这一切都归结为你的意图。

一方面,如果你想期望一个功能属于特定类型,那么它应该按照它的层次结构来实现,比如虚拟函数或成员指针等。从这个意义上说,限制是好的,因为它有助于使你的代码易于正确使用,而不易错误使用。

另一方面,如果你只想要一些抽象的"到此为止"功能,而不必担心它与特定基类紧密耦合的任何负担,那么显然其他东西会更合适,比如指向自由函数的指针,或者std::函数等。

这一切都是关于哪个更适合你的软件的任何特定部分的特定设计。

最新更新