介绍
似乎没有什么比委托更能引起 C++ 程序员的兴趣了。在其他语言中,委托是一流的功能,因此开发人员可以使用这些易于理解的结构。然而,在 C++ 中,委托不是本机可用的。然而,这并不能阻止我们程序员尝试模仿委托存储和调用任何可调用函数的简便性。
委托通常支持同步执行,即在调用时;绑定函数在调用者的控制线程中执行。在多线程应用程序中,指定目标函数和它应该执行的线程而不施加函数签名限制是理想的。该库执行将委托和所有参数数据发送到目标线程的繁重工作。本文背后的想法是提供一个具有一致 API 的 C++ 委托库,该 API 能够对任何可调用函数进行同步和异步调用。
现代 C++ 委托库的特点是:
- 任何编译器——适用于任何编译器的标准 C++17 代码,没有奇怪的黑客攻击
- 任何函数——调用任何可调用函数:成员函数、静态函数或自由函数
- 任何参数类型——支持任何参数类型:值、引用、指针、指向指针的指针
- 多个参数——支持绑定函数的 N 个函数参数
- 同步调用——同步调用绑定函数
- 异步调用——在客户端指定的线程上异步调用绑定函数
- 阻塞异步调用——使用阻塞或非阻塞委托异步调用
- 智能指针支持- 使用原始对象指针或绑定实例函数
std::shared_ptr
- 自动堆处理——自动将参数数据复制到堆中,以便通过消息队列进行安全传输
- 任何操作系统——轻松移植到任何操作系统。包括C++11
std::thread
端口 - Visual Studio 和 Eclipse - 包括 VC++ 和 GCC 项目
- 单元测试- 包括委托库的广泛单元测试
- 没有外部库——委托不依赖外部库
- 易于使用——函数签名模板参数(例如,
MulticastDelegate<void(TestStruct*)>
)
委托实现通过在您指定的控制线程上使用所有函数参数执行委托函数,显着简化了多线程应用程序开发。该框架处理所有低级机制以安全地调用目标线程上的任何函数签名。包含 Windows 2017 和 Eclipse 项目以方便实验。
代表背景
如果您不熟悉委托,这个概念非常简单。委托可以被认为是一个超级函数指针。在 C++ 中,没有指针类型能够指向所有可能的函数变体:实例成员、虚拟、常量、静态和自由(全局)。函数指针不能指向实例成员函数,指向成员函数的指针有各种限制。但是,如果函数签名匹配,委托类可以以类型安全的方式指向任何函数。简而言之,委托指向任何具有匹配签名的函数以支持匿名函数调用。
实际上,虽然委托很有用,但多播版本显着扩展了它的实用性。绑定多个函数指针并顺序调用所有注册器的能力构成了一种有效的发布者/订阅者机制。发布者代码公开一个委托容器,一个或多个匿名订阅者向发布者注册回调通知。
多线程系统上回调的问题,无论是基于委托还是基于函数指针,都在于回调是同步发生的。必须注意,不会在非线程安全的代码上调用来自另一个控制线程的回调。多线程应用程序开发很难。对原设计者来说很难;这很难,因为各种技能水平的工程师都必须维护代码;这很难,因为错误会以困难的方式表现出来。理想情况下,架构解决方案有助于最大限度地减少错误并简化应用程序开发。
此 C++ 委托实现功能齐全,允许使用任何参数同步或异步调用任何函数,甚至是实例成员函数。委托库使绑定和调用任何函数变得轻而易举。
使用代码
我将首先介绍如何使用代码,然后介绍实现细节。
委托库由委托和委托容器组成。委托能够绑定到单个可调用函数。多播委托容器将一个或多个委托保存在列表中以按顺序调用。一个 cast delegate 容器最多容纳一个 delegate。
下面列出了主要的委托类:
DelegateFree<>
DelegateFreeAsync<>
DelegateFreeAsyncWait<>
DelegateMember<>
DelegateMemberAsync<>
DelegateMemberAsyncWait<>
DelegateMemberSp<>
DelegateMemberSpAsync<>
DelegateFree<>
绑定到自由或静态成员函数。DelegateMember<>
绑定到类实例成员函数。DelegateMemberSp<>
使用std::shared_ptr
而不是原始对象指针绑定到类实例成员函数。所有版本都提供同步函数调用。
DelegateFreeAsync<>
,DelegateMemberAsync<>
并DelegateMemberSpAsync<>
以与同步对应物相同的方式运行;除了这些版本在指定的控制线程上提供非阻塞异步函数执行。
DelegateFreeAsyncWait<>
并DelegateMemberAsyncWait<>
在目标线程上提供阻塞异步函数执行,调用者提供最大等待超时。
三个主要的委托容器类是:
SinglecastDelegate<>
MulticastDelegate<>
MulticastDelegateSafe<>
SinglecastDelegate<>
是接受单个委托的委托容器。单一转换版本的优点是它稍微小一些并且允许返回类型而不是void
绑定函数。
MulticastDelegate<>
是一个委托容器,实现为接受多个委托的单链表。只能将绑定到具有void
返回类型的函数的委托添加到多播委托容器中。
MultcastDelegateSafe<>
是一个线程安全的容器,实现为接受多个委托的单链表。如果多个线程访问容器实例,请始终使用线程安全版本。
每个容器按值存储委托。这意味着委托被内部复制到堆或固定块内存中,具体取决于模式。用户不需要在插入容器之前在堆上手动创建委托。通常,重载模板函数MakeDelegate()
用于根据函数参数创建委托实例。
同步委托
所有委托都是使用重载MakeDelegate()
模板函数创建的。编译器使用模板参数推导来选择正确的MakeDelegate()
版本,无需手动指定模板参数。例如,这里有一个简单的自由函数。
要将自由函数绑定到委托,请DelegateFree<void(int)>
使用MakeDelegate()
. DelegateFree
模板参数是完整函数的签名:void(int)
. MakeDelegate()
返回一个DelegateFree<void(int)>
对象,下一行FreeFuncInt
使用委托调用该函数。
成员函数以相同的方式绑定到委托,只是这次MakeDelegate()
使用两个参数:类实例和成员函数指针。两个DelegateMember
模板参数是类名(即TestClass
)和绑定函数签名(即void(TestStruct*)
)。
与创建具体的自由或成员委托不同,通常使用委托容器来容纳一个或多个委托。委托容器可以容纳任何委托类型。例如,绑定到具有void (int)
函数签名的任何函数的多播委托容器如下所示:
单个 cast 委托以相同的方式创建:
返回值的函数签名也是可能的。委托容器接受带有一个float
参数的函数并返回一个int
.