├── .gitignore ├── LICENSE ├── README.md ├── asm_comparison.png ├── example └── tostring_async.cpp ├── future_detail.h ├── modern_callback.h ├── use_future.h └── use_librf.h /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017 lanzhengpeng 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modern Callback 2 | 3 | ## 问题起源 4 | 5 | 在异步编程的历史上,callback霸占了程序员相当长的时间。一般来说,callback有两种做法: 6 | 7 | 一、一次设置回调,多次调用。 8 | 9 | 这种模式常见于异步网络中。即便到现在也有大量程序员坚持这种用法。好处显然是不用浪费每次设置回调的开销。但对于复杂逻辑来说,就不太友好了。因为回调入口只有一个,要实现复杂的逻辑,只能自己在回调里想方设法折腾。今天我们要谈的不是这种类型。 10 | 11 | 二、一次设置回调,一次调用。 12 | 13 | 这种模式常用于文件IO,因为这种类似的操作不会有多次结果。坏处嘛,当然是会在设置回调上多了开销,但应对复杂的逻辑来说,具有相当好的深度可供挖掘。今天重点就是要谈这种类型的回调中的一种。 14 | 15 | 一次设置,一次调用,还可以根据对失败的处理,细分下使用方式: 16 | 17 | 1、设置时失败,则返回错误信息,同时不会调用回调接口; 18 | 19 | 2、异步操作失败,不会调用回调接口; 20 | 21 | 3、异步操作失败会调用回调接口,但是取消异步操作则不会调用回调接口; 22 | 23 | 4、任何情况下都会调用回调接口; 24 | 25 | 今天要谈的就是第4种使用方式。我认为前三种使用方式都是错误的。特别是第一种,将失败处理硬生生拆分成两部分逻辑,看不到任何好处。 26 | 27 | 那么,第4种异步回调的常用代码大致如下: 28 | 29 | ```c++ 30 | template 31 | void tostring_async(_Input_t&& value, _Callable_t&& callback) 32 | { 33 | std::thread([callback = std::forward(callback), value = std::forward<_Input_t>(value)] 34 | { 35 | callback(std::to_string(value)); 36 | }).detach(); 37 | } 38 | ``` 39 | 使用代码就不具体讨论了,但凡有异步编程经验的程序员都能想象该如何使用该函数。 40 | 41 | 然而,这样的回调用起来并没有那么美好。在复杂逻辑下,很快会陷入: 42 | 43 | !!!CALLBACK HELL!!! 44 | 45 | 为了解决这种深度的连续回调逻辑,聪明的程序员又发明了调用链来解决。通常就是future+then方案。代码大致如下: 46 | 47 | ```c++ 48 | template 49 | std::future tostring_async(_Input_t&& value) 50 | { 51 | return std::async([value = std::forward<_Input_t>(value)] 52 | { 53 | return std::to_string(value); 54 | }); 55 | } 56 | 57 | //使用范例代码 58 | tostring_async(1.0).then([](std::string && result) 59 | { 60 | std::cout << result << std::endl; 61 | }); 62 | ``` 63 | 然而,上面的代码目前并不能编译通过。因为std::future::then()是即将到来的C++20的功能。不管这段代码是否能编译通过,这种任务链的库还是挺多的,比较知名的如folly,以及比较不知名的libst。 64 | 65 | 再次然而,如果仅仅是非常非常深的连续回调,大部分程序员还能掌控,配合lambda食用,也不是什么难题。即便没有 future & then 可用的情况下,也只是要一个宽一点的显示器而已。现在的显示器都比较宽,不是问题。但是,要用回调模拟循环,然后再涉及到分支,大部分程序员就很难掌控了。即便诸如陈硕这样的大神,都讨厌这种逻辑。因为要实现循环分支,lambda就不是那么好用了。大体代码会长得像这个样子: 66 | 67 | ```c++ 68 | void do_accept(...) 69 | { 70 | if (!ec) 71 | do_read(...); 72 | } 73 | void do_read(...) 74 | { 75 | if (!ec) 76 | do_write(...); 77 | else 78 | do_accept(...); 79 | } 80 | void do_write(...) 81 | { 82 | if (!ec) 83 | do_read(...); 84 | } 85 | ``` 86 | 貌似走到了山穷水尽的末路! 87 | 88 | 但我们仔细想第二种做法的第4种使用方式,其内在逻辑,其实就是顺序执行逻辑。在不考虑异常的情况下,一行代码执行完毕后,必然会执行下一行代码,且只执行一次下一行代码。只不过在两行代码之间,插入了一个透明的延迟而已。那么,适用这种模式的解决方案就呼之欲出,那就是:协程!!! 89 | 90 | 哦,这个古老而富有魅力的美女,一下子就让我们拨云见日。赶紧的,支持下协程!以下代码以支持librf为例: 91 | 92 | ```c++ 93 | template 94 | librf::future_t tostring_async(_Input_t value) 95 | { 96 | librf::promise_t awaitable; 97 | std::thread([value, st = awaitable._state] 98 | { 99 | st->set_value(std::to_string(value)); 100 | }).detach(); 101 | 102 | return awaitable.get_future(); 103 | } 104 | 105 | //使用范例代码 106 | std::string result = co_await tostring_async(val); 107 | std::cout << result << std::endl; 108 | ``` 109 | 110 | 美,真美!循环,分支全都不是问题。简单到如初学写代码的码农都能掌握,谁还没写过顺序执行的代码呢?! 111 | 112 | 但是,等等,你的librf完善吗?稳定吗?高效吗?支持Linux吗?我想支持boost::fiber怎么办?我想支持libgo怎么办? 113 | 114 | 呵呵,能怎么办?赶紧回去996.ICU咯。我相信boost::fiber和libgo都提供了扩展的能力,你只需要翻一翻他们的文档,针对每一个异步函数写一遍适配代码,然后不要忘记写一遍测试范例。记得周末来加班哦! 115 | 116 | 但你大概率会在下周一继续犯愁。因为你用了一个第三方的异步库,你没权限,或者没源码,或者没能力(嗯有点尴尬),去扩展这个库。 117 | 118 | 哦,你都有啊,很好,赶紧回去996.ICU咯。我相信你通过一周的加班加点,终于完美的扩展好了这个第三方异步库,并且针对每个函数每个功能都写了严格的测试范例,完美的完成了所有功能,确保了99.99999%的可靠性(然而还是会崩溃一两次),下一个季度的KPI有指望了! 119 | 120 | 但你还是有一定概率在下下个周一遇上意外。因为第三方库的作者发布申明,发现了N个XXX漏洞,针对这些漏洞,做了YYY修改,涉及到M个ZZZ接口。你一看ZZZ接口列表,一口老血就喷在了显示器上,然后就觉得这个世界正在变得黑暗,声音也逐渐离你远去----你昏倒在了工位上。 121 | 122 | 从医院缓缓苏醒的你,并没有放弃。因为你是如此的坚(gu)持(zhi),是如此的优秀,以至于在昏倒的时候,耶稣上帝还有佛祖(哦还有三清),感动得给你指明了一条康庄大道。就等你恢复了身体能自己下床尿尿后,把这条“道”给写出来! 123 | 124 | 妈耶,写了这么多,居然还没有进入正题,罪过罪过。不过,答应人家江南的东西,也总不能老用忙来拖延吧?期望下周没那么忙,能把Modern Callback(下一篇)写完。 125 | 126 | ---------------------------------------我是分割线--------------------------------------- 127 | 128 | 129 | 130 | 131 | ## 解决方案 132 | 133 | 在上一篇文章中,乱七八糟的写了不少。主要讲述了使用原旨callback的异步库所面临的困难:callback本身难于使用,要将异步库改造得支持future-then范式,或者支持协程,需要更改每一个异步函数。而future库/协程库又选择繁多,要一一支持就需要一一的改写每个异步函数,导致工作量是**乘法**数量级。 134 | 135 | 那有没有一种办法,**将乘法拆解成加法,并且还能保护已有的异步库代码呢?**这就是我今天要讲的Modern Callback模式。 136 | 137 | ### 原旨callback 138 | 139 | 以一个异步转换字符串的函数为例,原旨主义的回调函数,大致长成这样: 140 | 141 | ```c++ 142 | template 143 | void tostring_async(_Input_t&& value, _Callable_t&& callback) 144 | { 145 | std::thread([callback = std::move(callback), value = std::forward<_Input_t>(value)] 146 | { 147 | callback(std::to_string(value)); 148 | }).detach(); 149 | } 150 | 151 | //使用范例 152 | tostring_async(-1.0, [](std::string && value) 153 | { 154 | std::cout << value << std::endl; 155 | }); 156 | ``` 157 | 158 | ### Modern callback 159 | 160 | 那么,要改造成现代的回调,应该怎么做呢?我给出了这样一个方案: 161 | 162 | ```C++ 163 | //回调适配器的模板类 164 | //这个默认类以_Callable_t作为真正的回调 165 | //返回无意义的int,以便于编译通过 166 | template 167 | struct modern_callback_adapter_t 168 | { 169 | using return_type = int; 170 | using callback_type = _Callable_t; 171 | 172 | static std::tuple traits(_Callable_t&& callback) 173 | { 174 | return { std::forward<_Callable_t>(callback), 0 }; 175 | } 176 | }; 177 | 178 | //一个使用回调处理结果的异步函数,会涉及以下概念: 179 | //_Input_t...:异步函数的输入参数; 180 | //_Signature_t: 此异步回调的函数签名;应当满足‘void(_Exception_t, _Result_t...)’或者‘void(_Result_t...)’类型; 181 | //_Callable_t:回调函数或标记,如果是回调函数,则需要符合_Signature_t的签名类型。这个回调,必须调用一次,且只能调用一次; 182 | //_Return_t:异步函数的返回值; 183 | //_Result_t...:异步函数完成后的结果值,作为回调函数的入参部分;这个参数可以有零至多个; 184 | //_Exception_t:回调函数的异常, 如果不喜欢异常的则忽略这个部分,但就得异步代码将异常处置妥当; 185 | // 186 | //在回调适配器模型里,_Input_t/_Result_t/_Exception_t(可选)是异步函数提供的功能所固有的部分;_Callable_t/_Return_t 187 | //部分并不直接使用,而是通过适配器去另外处理。这样给予适配器一次扩展到future模式,调用链模式的机会,以及支持协程的机会。 188 | // 189 | //tostring_async 演示了在其他线程里,将_Input_t的输入值,转化为std::string类型的_Result_t。 190 | //然后调用_Signature_t为 ‘void(std::string &&)’ 类型的 _Callable_t。 191 | //忽视异常处理,故没有_Exception_t。 192 | template 193 | auto tostring_async(_Input_t&& value, _Callable_t&& callback) 194 | //-> typename modern_callback_adapter_t, std::string>::return_type 195 | { 196 | using _Result_t = std::string; 197 | //适配器类型 198 | using _Adapter_t = modern_callback_adapter_t, _Result_t>; 199 | //通过适配器获得兼容_Callable_t类型的真正的回调,以及返回值_Return_t 200 | auto adapter = typename _Adapter_t::traits(std::forward<_Callable_t>(callback)); 201 | 202 | //real_callback与callback未必是同一个变量,甚至未必是同一个类型 203 | std::thread([real_callback = std::move(std::get<0>(adapter)), value = std::forward<_Input_t>(value)] 204 | { 205 | real_callback(std::to_string(value)); 206 | }).detach(); 207 | 208 | //返回适配器的return_type变量 209 | return std::move(std::get<1>(adapter)); 210 | } 211 | ``` 212 | 213 | 看似增加了不少代码,但这些代码很模式化,完全可以用宏来简化(哎,又爱又恨的宏): 214 | 215 | ```c++ 216 | #define MODERN_CALLBACK_TRAITS(type) \ 217 | using _Result_t = type; \ 218 | using _Adapter_t = modern_callback_adapter_t, _Result_t>; \ 219 | auto adapter = typename _Adapter_t::traits(std::forward<_Callable_t>(callback)) 220 | #define MODERN_CALLBACK_CALL() callback = std::move(std::get<0>(adapter)) 221 | #define MODERN_CALLBACK_RETURN() return std::move(std::get<1>(adapter)) 222 | 223 | template 224 | auto tostring_async(_Input_t&& value, _Callable_t&& callback) 225 | { 226 | MODERN_CALLBACK_TRAITS(std::string); 227 | 228 | std::thread([MODERN_CALLBACK_CALL(), value = std::forward<_Input_t>(value)] 229 | { 230 | callback(std::to_string(value)); 231 | }).detach(); 232 | 233 | MODERN_CALLBACK_RETURN(); 234 | } 235 | ``` 236 | 237 | 这样的修改,并不会大动干戈的修改现有的逻辑和代码。有N个支持回调的异步函数,也仅做N次修改,工作量并不大。 238 | 239 | 显然,通用的modern_callback_adapter_t没有做任何有效的事情,仅仅将callback转移了两次,不会影响到已有逻辑。现有的使用原旨callback范式的代码也不会受到任何影响,该怎么使用就还使用。 240 | 241 | ### 支持future-then 242 | 243 | 首先,看看不采用morden callback方案,需要如何支持future-then范式: 244 | 245 | ```c++ 246 | template 247 | auto tostring_async(_Input_t&& value) 248 | { 249 | std::promise _promise; 250 | std::future _future = _promise.get_future(); 251 | 252 | std::thread([_promise = std::move(_promise), value = std::forward<_Input_t>(value)]() mutable 253 | { 254 | _promise.set_value(std::to_string(value)); 255 | }).detach(); 256 | 257 | return std::move(_future); 258 | } 259 | ``` 260 | 261 | 代码倒是不复杂,也是需要针对N个异步函数做修改,工作量一个没少。但是,别忘记了future库可是有不少。 262 | 263 | 下面演示如何扩展tostring_async函数,以支持future模式: 264 | 265 | ```c++ 266 | //一、做一个辅助类 267 | struct use_future_t {}; 268 | //二、申明这个辅助类的全局变量。不申明这个变量也行,就是每次要写use_future_t{},麻烦些。 269 | //以后就使用use_future,替代tostring_async的callback参数了。 270 | //这个参数其实不需要实质传参,最后会被编译器优化没了。 271 | //仅仅是要指定_Callable_t的类型为use_future_t, 272 | //从而在tostring_async函数内,使用偏特化的modern_callback_adapter_t版本而已。 273 | inline constexpr use_future_t use_future{}; 274 | 275 | //将替换use_future_t的,真正的回调类。 276 | //此回调类,符合tostring_async的_Callable_t函数签名。 277 | //生成此类的实例作为real_callback交给tostring_async作为异步回调。 278 | // 279 | //future模式下,此类持有一个std::promise<_Result_t>,便于设置值和异常 280 | //而将与promise关联的future作为返回值_Return_t,让tostring_async返回。 281 | template 282 | struct use_future_callback_t 283 | { 284 | using promise_type = std::promise<_Result_t>; 285 | 286 | mutable promise_type _promise; 287 | 288 | void operator()(_Result_t&& value) const 289 | { 290 | _promise.set_value(value); 291 | } 292 | 293 | void operator()(_Result_t&& value, std::exception_ptr&& eptr) const 294 | { 295 | if (eptr != nullptr) 296 | _promise.set_exception(std::forward(eptr)); 297 | else 298 | _promise.set_value(std::forward<_Result_t>(value)); 299 | } 300 | }; 301 | 302 | //偏特化_Callable_t为use_future_t类型的modern_callback_adapter_t 303 | //真正的回调类型是use_future_callback_t,返回类型_Return_t是std::future<_Result_t>。 304 | //配合use_future_callback_t的std::promise<_Result_t>,正好组成一对promise/future对。 305 | //promise在真正的回调里设置结果值; 306 | //future返回给调用者获取结果值。 307 | template 308 | struct modern_callback_adapter_t 309 | { 310 | using return_type = std::future<_Result_t>; 311 | using callback_type = use_future_callback_t<_Result_t>; 312 | 313 | static std::tuple traits(const use_future_t&/*没人关心这个变量*/) 314 | { 315 | callback_type real_callback{}; 316 | return_type future = real_callback._promise.get_future(); 317 | 318 | return { std::move(real_callback), std::move(future) }; 319 | } 320 | }; 321 | ``` 322 | 323 | 哦,看起来代码增加了不少。但是,这样的代码只需要针对所选择的future库写一次,就可以支持全部的N个异步函数了。使用范例如下: 324 | 325 | ```c++ 326 | std::future f2 = tostring_async(6.0f, use_future); 327 | std::cout << f2.get() << std::endl; 328 | ``` 329 | 330 | ### 协程中使用 331 | 332 | 我们的终极目的是协程,只有使用了协程后,才可以非常容易的支持循环+分支逻辑。下面以librf为例,看看如何去支持协程: 333 | 334 | ```c++ 335 | //同理,可以制作支持C++20的协程的下列一系列类(其实,这才是我的最终目的) 336 | struct use_awaitable_t {}; 337 | inline constexpr use_awaitable_t use_awaitable{}; 338 | 339 | template 340 | struct use_awaitable_callback_t 341 | { 342 | using promise_type = librf::promise_t<_Result_t>; 343 | using state_type = typename promise_type::state_type; 344 | 345 | librf::counted_ptr _state; 346 | 347 | void operator()(_Result_t&& value) const 348 | { 349 | _state->set_value(std::forward<_Result_t>(value)); 350 | } 351 | void operator()(_Result_t&& value, std::exception_ptr&& eptr) const 352 | { 353 | if (eptr != nullptr) 354 | _state->set_exception(std::forward(eptr)); 355 | else 356 | _state->set_value(std::forward<_Result_t>(value)); 357 | } 358 | }; 359 | 360 | template 361 | struct modern_callback_adapter_t 362 | { 363 | using promise_type = librf::promise_t<_Result_t>; 364 | using return_type = librf::future_t<_Result_t>; 365 | using callback_type = use_awaitable_callback_t<_Result_t>; 366 | 367 | static std::tuple traits(const use_awaitable_t&) 368 | { 369 | promise_type promise; 370 | return { callback_type{ promise._state }, promise.get_future() }; 371 | } 372 | }; 373 | ``` 374 | 375 | 不要看见这些代码就害怕,记住,这样的代码只写一次!O(1)的算法就是最好的,所以,只写一次的代码也是最好的!下面看看如何使用: 376 | 377 | ```c++ 378 | std::string result = co_await tostring_async(10.0, use_awaitable); 379 | std::cout << result << std::endl; 380 | ``` 381 | 382 | 仅仅是替换callback参数,就达到了使用协程的目的。并且,只需要为选择的协程库写一次适配代码就可以了。 383 | 384 | 对比下代码量的修改: 385 | 386 | 异步库有N个异步函数 387 | 388 | 有三种异步支持方案:callback,future-then,coroutine,其中,可选择的库有folly,libst,libgo,librf等K个。 389 | 390 | 则原旨主义的回调方案,需要做N*K次修改。 391 | 而采用modern callback方案,只需要做N+K次适配。 392 | 393 | 显然,这是一个更好的方案。 394 | 395 | 并且,当异步库修改了M个异步函数后 396 | 397 | 原旨主义的callback方案,需要做M*K次修改; 398 | 而modern callback不需要在适配上做任何修改,编译一次就好了。 399 | 400 | ### 运行效率 401 | 402 | 那么,modern callback引入的modern_callback_adapter_t是不是会严重影响效率呢?答案是:不会的。我们以编译出来的汇编代码为例: 403 | 404 | ![asm_comparison](asm_comparison.png) 405 | 406 | 可以看到,modern callback对比originalism callback,仅仅多引入了一个'xor eax,eax'指令。 407 | 408 | (在最终版本,多出来的'xor eax, eax'也被消除了) 409 | 410 | 411 | ## 总结 412 | 413 | 可以看到,modern callback通过增加一个适配器,做微量代码的修改情况下,既能保护现有的callback逻辑不变,又能增加对future-then模式,对coroutine的支持。**将原来需要乘法数量级的修改,变成加法数量级的修改**,减少代码的同时,大家也更好的955.WLB。 414 | 本文章的范例代码:[morden_cb](https://github.com/tearshark/modern_cb) 415 | 416 | 最后,感谢江南的发稿邀请,让我有动力将modern callback想法写成文章,这原本是在QQ群(296561497)里的一场讨论。欢迎大家加入此群,此群里管理严格,氛围良好,并且绝对不开车,是一个学习Modern C++的好去处! 417 | 418 | 419 | 420 | 2019年4月25日更新: 421 | 本文章的范例代码:[modern_cb](https://github.com/tearshark/modern_cb) 已经更新,详情请点击链接查看。 422 | 范例代码不再考虑写文章的需要而进行简化了,因此,可能会更难看一些。但增加了下列功能: 423 | 一、增加了对异常/错误码的支持; 424 | 二、增加了对多结果值的支持; 425 | 三、用一套代码对兼容std::future用法的库提供了支持,使得扩展兼容std::future用法的代码更加简单; 426 | 四、适配器使用间接返回,消除了多余的'xor eax, eax'代码,使得Modern Callback效率完全等同与原旨主义的callback; 427 | 428 | 429 | 430 | 注一:本文最先发表于知乎,随后发表于purecpp。原文链接: 431 | 432 | 433 | 434 | 435 | 注二:范例代码不再考虑写文章的需要而进行简化了,因此,可能会更难看一些。但增加了下列功能: 436 | 437 | 一、增加了对异常/错误码的支持; 438 | 二、增加了对多结果值的支持; 439 | 三、用一套代码对兼容std::future用法的库提供了支持,使得扩展兼容std::future用法的代码更加简单; 440 | 四、适配器使用间接返回,消除了多余的'xor eax, eax'代码,使得Modern Callback效率完全等同与原旨主义的callback; 441 | -------------------------------------------------------------------------------- /asm_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tearshark/modern_cb/2673359ee09453caf0da95cebaba774652caa8b9/asm_comparison.png -------------------------------------------------------------------------------- /example/tostring_async.cpp: -------------------------------------------------------------------------------- 1 | //依赖 https://github.com/tearshark/modern_cb 项目 2 | //依赖 https://github.com/tearshark/librf 项目 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "modern_callback.h" 9 | 10 | //原旨主义的异步函数,其回调写法大致如下 11 | template 12 | void tostring_async_originalism(_Input_t&& value, _Callable_t&& token) 13 | { 14 | std::thread([callback = std::move(token), value = std::forward<_Input_t>(value)] 15 | { 16 | callback(std::to_string(value)); 17 | }).detach(); 18 | } 19 | 20 | //使用原旨主义的方式扩展异步方法来支持future 21 | template 22 | auto tostring_async_originalism_future(_Input_t&& value) 23 | { 24 | std::promise _promise; 25 | std::future _future = _promise.get_future(); 26 | 27 | std::thread([_promise = std::move(_promise), value = std::forward<_Input_t>(value)]() mutable 28 | { 29 | _promise.set_value(std::to_string(value)); 30 | }).detach(); 31 | 32 | return std::move(_future); 33 | } 34 | 35 | //---------------------------------------------------------------------------------------------------------------------- 36 | //下面演示如何扩展tostring_async函数,以支持future模式 37 | 38 | template 39 | auto tostring_async(_Input_t&& value, _Callable_t&& token) 40 | { 41 | MODERN_CALLBACK_TRAITS(token, void(std::string)); 42 | 43 | std::thread([callback = MODERN_CALLBACK_CALL(), value = std::forward<_Input_t>(value)] 44 | { 45 | callback(std::to_string(value)); 46 | }).detach(); 47 | 48 | MODERN_CALLBACK_RETURN(); 49 | } 50 | 51 | //演示异步库有多个异步回调函数,只要按照Modern Callback范式去做回调,就不再需要写额外的代码,就可以适配到future+librf,以及更多的其他库 52 | template 53 | auto add_async(_Ty1&& val1, _Ty2&& val2, _Callable_t&& token) 54 | { 55 | MODERN_CALLBACK_TRAITS(token, void(decltype(val1 + val2))); 56 | 57 | std::thread([=, callback = MODERN_CALLBACK_CALL()] 58 | { 59 | using namespace std::literals; 60 | std::this_thread::sleep_for(0.1s); 61 | callback(val1 + val2); 62 | }).detach(); 63 | 64 | MODERN_CALLBACK_RETURN(); 65 | } 66 | 67 | //演示异步库有多个异步回调函数,只要按照Modern Callback范式去做回调,就不再需要写额外的代码,就可以适配到future+librf,以及更多的其他库 68 | template 69 | auto muldiv_async(_Ty1&& val1, _Ty2&& val2, _Callable_t&& token) 70 | { 71 | MODERN_CALLBACK_TRAITS(token, void(std::exception_ptr, decltype(val1 * val2), decltype(val1 / val2))); 72 | 73 | std::thread([=, callback = MODERN_CALLBACK_CALL()] 74 | { 75 | using namespace std::literals; 76 | std::this_thread::sleep_for(0.1s); 77 | 78 | auto v1 = val1 * val2; 79 | 80 | if (val2 == 0) 81 | callback(std::make_exception_ptr(std::logic_error("divided by zero")), v1, 0); 82 | else 83 | callback(nullptr, v1, val1 / val2); 84 | }).detach(); 85 | 86 | MODERN_CALLBACK_RETURN(); 87 | } 88 | 89 | #include "use_future.h" 90 | 91 | static void example_future() 92 | { 93 | using namespace std::literals; 94 | 95 | //使用lambda作为异步回调函数,传统用法 96 | tostring_async_originalism(-1.0, [](std::string&& value) 97 | { 98 | std::cout << value << std::endl; 99 | }); 100 | std::this_thread::sleep_for(0.5s); 101 | 102 | tostring_async(1.0, [](std::string&& value) 103 | { 104 | std::cout << value << std::endl; 105 | }); 106 | 107 | std::this_thread::sleep_for(0.5s); 108 | std::cout << "......" << std::endl; 109 | 110 | //支持future的用法 111 | std::future f1 = tostring_async_originalism_future(5); 112 | std::cout << f1.get() << std::endl; 113 | 114 | std::future f2 = tostring_async(6.0f, std_future); 115 | std::cout << f2.get() << std::endl; 116 | } 117 | 118 | #include "librf.h" 119 | #include "use_librf.h" 120 | 121 | static void example_librf() 122 | { 123 | //支持librf的用法 124 | GO 125 | { 126 | try 127 | { 128 | int val = co_await add_async(1, 2, use_librf); 129 | std::cout << val << std::endl; 130 | 131 | //muldiv_async函数可能会抛异常,取决于val是否是0 132 | //异常将会带回到本协程里的代码,所以需要try-catch 133 | auto [a, b] = co_await muldiv_async(9, val, use_librf); 134 | 135 | std::string result = co_await tostring_async(a + b, use_librf); 136 | 137 | std::cout << result << std::endl; 138 | } 139 | catch (const std::exception & e) 140 | { 141 | std::cout << "exception signal : " << e.what() << std::endl; 142 | } 143 | catch (...) 144 | { 145 | std::cout << "exception signal : who knows?" << std::endl; 146 | } 147 | }; 148 | 149 | librf::this_scheduler()->run_until_notask(); 150 | } 151 | 152 | int main(int argc, char* argv[]) 153 | { 154 | example_future(); 155 | example_librf(); 156 | } 157 | -------------------------------------------------------------------------------- /future_detail.h: -------------------------------------------------------------------------------- 1 | // 2 | //通过现代回调(Modern Callback), 使用回调适配器模型, 3 | //将异步回调函数扩展到支持future模式,调用链模式,以及协程。 4 | // 5 | //future库有多种,但应当都提供遵循promise/future对,兼容std::promise/std::future用法 6 | //这样的话,可以做一个更加通用的支持future的callback类 7 | 8 | #pragma once 9 | 10 | namespace modern_callback 11 | { 12 | namespace future_detail 13 | { 14 | //由于std::function<>只接受 CopyConstructible 且 CopyAssignable 的 Callable target 15 | //故需要 callback_base_t 也支持 CopyConstructible 和 CopyAssignable 16 | //而常见的 promise 实现,如 std::promise<> 是不支持 CopyConstructible 和 CopyAssignable 17 | //所以,promise_copyable_storage_t 提供一个支持 CopyConstructible 和 CopyAssignable 的存储类 18 | template && std::is_copy_assignable_v<_Promise_type>> 19 | struct promise_copyable_storage_t 20 | { 21 | using type = _Promise_type; 22 | 23 | std::shared_ptr<_Promise_type> _value{ std::make_shared<_Promise_type>() }; 24 | 25 | _Promise_type& ref_value() 26 | { 27 | return *_value; 28 | } 29 | _Promise_type move_value() 30 | { 31 | return std::move(*_value); 32 | } 33 | }; 34 | 35 | template 36 | struct promise_copyable_storage_t<_Promise_type, true> 37 | { 38 | using type = _Promise_type; 39 | 40 | _Promise_type _value; 41 | 42 | _Promise_type& ref_value() 43 | { 44 | return _value; 45 | } 46 | _Promise_type move_value() 47 | { 48 | return std::move(_value); 49 | } 50 | }; 51 | 52 | //实现callback_t的基类,避免写一些重复代码 53 | template 54 | struct callback_base_t 55 | { 56 | //回调函数的结果类型,已经排除掉了异常参数 57 | using result_type = _Result_t; 58 | 59 | //通过_Promise_traits获取真正的promise类型 60 | using promise_type = typename _Promise_traits::template promise_type; 61 | 62 | //通过_Promise_traits获取真正的future类型 63 | using future_type = typename _Promise_traits::template future_type; 64 | 65 | //此类持有一个std::promise<_Result_t>,便于设置值和异常 66 | //而将与promise关联的future作为返回值_Return_t,让tostring_async返回。 67 | mutable promise_copyable_storage_t _promise; 68 | 69 | auto get_future() const 70 | { 71 | return this->_promise.ref_value().get_future(); 72 | } 73 | }; 74 | 75 | //此类的实例作为真正的callback,交给异步回调函数,替换token。 76 | //在实际应用中,需要针对是否有异常参数,结果值为0,1,多个等情况做特殊处理,故还需要通过更多的偏特化版本来支持。 77 | //具体的异常参数,需要根据实际应用去特里化。这里仅演示通过std::exception_ptr作为异常传递的情况。 78 | template 79 | struct callback_t; 80 | 81 | //无异常,无结果的callback类型:void() 82 | template 83 | struct callback_t<_Promise_traits> : public callback_base_t<_Promise_traits, void> 84 | { 85 | using callback_base_t<_Promise_traits, void>::callback_base_t; 86 | 87 | void operator()() const 88 | { 89 | auto&& p = this->_promise.move_value(); //杜绝可能this在回调中被析构 90 | p.set_value(); 91 | } 92 | }; 93 | 94 | //有异常,无结果的callback类型:void(exception_ptr) 95 | template 96 | struct callback_t<_Promise_traits, std::exception_ptr> : public callback_base_t<_Promise_traits, void> 97 | { 98 | using callback_base_t<_Promise_traits, void>::callback_base_t; 99 | 100 | void operator()(std::exception_ptr eptr) const 101 | { 102 | auto&& p = this->_promise.move_value(); 103 | if (!eptr) 104 | p.set_value(); 105 | else 106 | p.set_exception(std::move(eptr)); 107 | } 108 | }; 109 | 110 | //无异常,单结果的callback类型:void(_Result_t) 111 | template 112 | struct callback_t<_Promise_traits, _Result_t> : public callback_base_t<_Promise_traits, _Result_t> 113 | { 114 | using callback_base_t<_Promise_traits, _Result_t>::callback_base_t; 115 | 116 | template 117 | void operator()(Arg&& arg) const 118 | { 119 | auto&& p = this->_promise.move_value(); 120 | p.set_value(std::forward(arg)); 121 | } 122 | }; 123 | 124 | //有异常,单结果的callback类型:void(std::exception_ptr, _Result_t) 125 | template 126 | struct callback_t<_Promise_traits, std::exception_ptr, _Result_t> : public callback_base_t<_Promise_traits, _Result_t> 127 | { 128 | using callback_base_t<_Promise_traits, _Result_t>::callback_base_t; 129 | 130 | template 131 | void operator()(std::exception_ptr eptr, Arg&& arg) const 132 | { 133 | auto&& p = this->_promise.move_value(); 134 | if (!eptr) 135 | p.set_value(std::forward(arg)); 136 | else 137 | p.set_exception(std::move(eptr)); 138 | } 139 | }; 140 | 141 | //无异常,多结果的callback类型:void(_Result_t...) 142 | template 143 | struct callback_t<_Promise_traits, _Result_t...> : public callback_base_t<_Promise_traits, std::tuple<_Result_t...> > 144 | { 145 | using callback_base_t<_Promise_traits, std::tuple<_Result_t...> >::callback_base_t; 146 | 147 | template 148 | void operator()(Args&&... args) const 149 | { 150 | static_assert(sizeof...(Args) == sizeof...(_Result_t), ""); 151 | auto&& p = this->_promise.move_value(); 152 | p.set_value(std::make_tuple(std::forward(args)...)); 153 | } 154 | }; 155 | 156 | //有异常,多结果的callback类型:void(std::exception_ptr, _Result_t...) 157 | template 158 | struct callback_t<_Promise_traits, std::exception_ptr, _Result_t...> : public callback_base_t<_Promise_traits, std::tuple<_Result_t...> > 159 | { 160 | using callback_base_t<_Promise_traits, std::tuple<_Result_t...> >::callback_base_t; 161 | 162 | template 163 | void operator()(std::exception_ptr eptr, Args&&... args) const 164 | { 165 | static_assert(sizeof...(Args) == sizeof...(_Result_t), ""); 166 | auto&& p = this->_promise.move_value(); 167 | if (!eptr) 168 | p.set_value(std::make_tuple(std::forward(args)...)); 169 | else 170 | p.set_exception(std::move(eptr)); 171 | } 172 | }; 173 | 174 | 175 | 176 | //与callback_t配套的获得_Return_t的类 177 | template 178 | struct return_t 179 | { 180 | using result_type = _Result_t; 181 | using future_type = typename _Future_traits::template future_type; 182 | future_type _future; 183 | 184 | return_t(future_type&& ft) 185 | : _future(std::move(ft)) {} 186 | 187 | future_type get() 188 | { 189 | return std::move(_future); 190 | } 191 | }; 192 | 193 | 194 | //利用callback_t + return_t 实现的callback适配器 195 | template 196 | struct adapter_impl_t 197 | { 198 | using traits_type = std::remove_reference_t<_Token_as_callable_t>; 199 | using callback_type = callback_t; 200 | using return_type = return_t; 201 | using result_type = typename return_type::future_type; 202 | using future_type = typename return_type::future_type; 203 | 204 | static std::tuple traits(const _Token_as_callable_t& /*没人关心这个变量*/) 205 | { 206 | callback_type callback{}; 207 | auto future = callback.get_future(); 208 | 209 | return { std::move(callback), return_type{std::move(future)} }; 210 | } 211 | }; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /modern_callback.h: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright 2020 lanzhengpeng 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | 17 | // Modern Callback 18 | // 19 | // An asynchronous function that uses callbacks to process results will involve 20 | // the following concepts: 21 | // _Input_t...: Input parameters of asynchronous functions; 22 | // _Signature_t: The function signature of this asynchronous callback; 23 | // should meet the type of 'void (_Exception_t, _Result_t ...)' or 'void (_Result_t ...)’ 24 | // _Callable_t: Callback function or mark, if it is a callback function, 25 | // it needs to comply with the signature type of _Signature_t. 26 | // This callback must be called once, and only once; 27 | // _Return_t: The return value of the asynchronous function; 28 | // _Result_t...: The result value after the completion of the asynchronous function is 29 | // used as the input parameter of the callback function; this parameter can have zero or more; 30 | // _Exception_t: The exception of the callback function, if you don't like the exception, 31 | // ignore this part, but you have to handle the exception properly with the asynchronous code; 32 | // 33 | // In the callback adapter model, The '_Input_t.../_Result_t/_Exception_t(Optional)' is 34 | // an inherent part of the functionality provided by asynchronous functions; 35 | // The '_Callable_t / _Return_t' part is not used directly, but is handled separately by the adapter. 36 | // This gives the adapter an opportunity to expand into future mode, 37 | // call chain mode, and support coroutines. 38 | 39 | #pragma once 40 | #ifndef MODERN_CALLBACK_HEADER_FILE 41 | #define MODERN_CALLBACK_HEADER_FILE 42 | 43 | #include 44 | 45 | // Prepare return_void_t and adapter_t for asynchronous functions 46 | namespace modern_callback { 47 | // Solve the syntax problem of returning void through an indirect class in order 48 | // to optimize the return value 49 | struct return_void_t { 50 | void get() {} 51 | }; 52 | 53 | // Callback adapter template class 54 | // _Callable_t must conform to _Signature_t signature 55 | // This class does not do any effective work except for transferring tokens 56 | // Real work waits for specialized classes to do 57 | template 58 | struct adapter_t { 59 | using callback_type = _Callable_t; 60 | using return_type = return_void_t; 61 | using result_type = void; 62 | using future_type = void; 63 | 64 | template 65 | static std::tuple traits(_Callable2_t&& token) { 66 | return { std::forward<_Callable2_t>(token), {} }; 67 | } 68 | }; 69 | } 70 | 71 | #define MODERN_CALLBACK_TRAITS(_Token_value, _Signature_t) \ 72 | using _Adapter_t__ = modern_callback::adapter_t< \ 73 | std::decay_t<_Callable_t>, _Signature_t>; \ 74 | auto _Adapter_value__ = \ 75 | _Adapter_t__::traits(std::forward<_Callable_t>(_Token_value)) 76 | 77 | #define MODERN_CALLBACK_CALL() std::move(std::get<0>(_Adapter_value__)) 78 | 79 | #define MODERN_CALLBACK_RETURN() \ 80 | return std::move(std::get<1>(_Adapter_value__)).get() 81 | 82 | #define MODERN_CALLBACK_RESULT_TYPE(_Signature_t) \ 83 | typename modern_callback::adapter_t< \ 84 | std::decay_t<_Callable_t>, _Signature_t>::future_type 85 | 86 | #define MODERN_CALLBACK_CALLBACK_TYPE(_Signature_t) \ 87 | typename modern_callback::adapter_t< \ 88 | std::decay_t<_Callable_t>, _Signature_t>::callback_type 89 | 90 | 91 | 92 | 93 | #if 0 94 | //tostring_async demonstrates that in other threads, the input value of _Input_t is converted to _Result_t of type std :: string. 95 | //Then call _Signable_t of type ‘void (std :: string &&)’ _Callable_t. 96 | //Ignore exception handling, so there is no _Exception_t. 97 | // 98 | template 99 | auto tostring_async(_Input_t&& value, _Callable_t&& token) 100 | { 101 | //Adapter type 102 | using _Adapter_t = modern_callback::adapter_t>, void(std::string)>; 103 | //Get real callback compatible with _Signature_t type and return value _Return_t through adapter 104 | auto adapter = _Adapter_t::traits(std::forward<_Callable_t>(token)); 105 | 106 | //callback and token may not be the same variable, or even the same type 107 | std::thread([callback = std::move(std::get<0>(adapter)), value = std::forward<_Input_t>(value)] 108 | { 109 | using namespace std::literals; 110 | std::this_thread::sleep_for(0.1s); 111 | callback(std::to_string(value)); 112 | }).detach(); 113 | 114 | //Return the adapter's _Return_t variable 115 | return std::move(std::get<1>(adapter)).get(); 116 | } 117 | #endif 118 | 119 | #endif // MODERN_CALLBACK_HEADER_FILE 120 | -------------------------------------------------------------------------------- /use_future.h: -------------------------------------------------------------------------------- 1 | // 2 | //通过现代回调(Modern Callback), 使用回调适配器模型, 3 | //将异步回调函数扩展到支持future模式,调用链模式,以及协程。 4 | 5 | #pragma once 6 | #include 7 | #include "future_detail.h" 8 | 9 | namespace modern_callback 10 | { 11 | namespace detail 12 | { 13 | //一、做一个使用std::promise/std::future的辅助类。 14 | //这个类还负责萃取promise/future对的类型。 15 | struct std_future_t 16 | { 17 | template 18 | using promise_type = std::promise<_Result_t>; 19 | 20 | template 21 | using future_type = std::future<_Result_t>; 22 | }; 23 | } 24 | 25 | //二、偏特化_Callable_t为std_future_t类型的adapter_t 26 | //真正的回调类型是callback_t,返回类型_Return_t是return_t。 27 | //配合callback_t的promise,和return_t的future,正好组成一对promise/future对。 28 | //promise在真正的回调里设置结果值; 29 | //future返回给调用者获取结果值。 30 | template 31 | struct adapter_t : public future_detail::adapter_impl_t 32 | { 33 | }; 34 | } 35 | 36 | //三、申明这个辅助类的全局变量。不申明这个变量也行,就是每次要写use_future_t{},麻烦些。 37 | //以后就使用std_future,替代异步函数的token参数了。 38 | //这个参数其实不需要实质传参,最后会被编译器优化没了。 39 | //仅仅是要指定_Callable_t的类型为std_future_t, 40 | //从而在异步函数内,使用偏特化的adapter_t而已。 41 | constexpr modern_callback::detail::std_future_t std_future{}; 42 | -------------------------------------------------------------------------------- /use_librf.h: -------------------------------------------------------------------------------- 1 | // 2 | //通过现代回调(Modern Callback), 使用回调适配器模型, 3 | //将异步回调函数扩展到支持future模式,调用链模式,以及协程。 4 | 5 | #pragma once 6 | #include "future_detail.h" 7 | 8 | namespace modern_callback 9 | { 10 | namespace detail 11 | { 12 | //同理,可以制作支持C++20的协程的下列一系列类(其实,这才是我的最终目的) 13 | struct use_librf_t 14 | { 15 | template 16 | using promise_type = librf::awaitable_t<_Result_t>; 17 | 18 | template 19 | using future_type = librf::future_t<_Result_t>; 20 | }; 21 | } 22 | 23 | template 24 | struct adapter_t : public future_detail::adapter_impl_t 25 | { 26 | }; 27 | 28 | //所以,我现在的看法是,支持异步操作的库,尽可能如此设计回调。这样便于支持C++20的协程。以及future::then这样的任务链。 29 | //这才是“摩登C++”! 30 | } 31 | 32 | constexpr modern_callback::detail::use_librf_t use_librf{}; 33 | --------------------------------------------------------------------------------