std::function<>
模板到底是什么? 可以简单理解为对应 C 语言中的函数指针。入参或绑定的参数如果是原始指针或引用时,需要特别注意其生存周期。
关于函数类模板,学习《Effective.Modern.C++》P39
And maybe now you’re thinking “What’s a
std::function
object?” So let’s clear that up
boost::empty_value
的意义?
待补充
获得 std::function
对象
Lambda expressions
Constructs a closure: an unnamed function object capable of capturing variables in scope. 摘自 lambda
Function objects
A function object is any object for which the function call operator is defined. 摘自 functional
std::function
,推荐查看其 Example
Class template
std::function
is a general-purpose polymorphic function wrapper. Instances ofstd::function
can store, copy, and invoke any CopyConstructible Callable target – functions, lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to data members. 摘自 function
需要强调的是 C++ 标准未对 std::bind
表达式的返回类型做出定义,其返回值并不对应我们熟悉的任何类型,但可以直接赋值给 std::function<>
1 | std::function<int(int)> func = std::tolower; |
模板参数
1 | // ERR,末尾多余的 const 造成报错内容在千里之外 |
递归调用
为什么 function<>
对象递归调用自身不能使用 auto
?
vc2015 IntelliSence 提示
使用
auto
类型说明符声明的变量不能出现在其自身的初始值设定项中
1 | // auto sum2 = [sum2](int a) { // 编译错误 why? |
虽然 &sum2
引用能够调用成功,但我们必须时刻关注 sum2 对象的生存周期。比如以下代码就会崩溃
1 | std::function<int(int)> sum2Copy; |
对比虚函数
存在神话面向对象,过度使用继承的一个时代,一批人。他们使用基础类型指针,通过虚函数约定回调格式。
上述做法的限制:
- 局限于继承回调类,重写唯一的虚函数,当类中存在多种请求(大多情形如此)时,在同一回调函数中做分支处理(缺点),分支处理依赖
CBD
枚举类型,以此判断应该使用哪种数据交换类解析void*
指针
改用 std::function
类模板带来的好处:
- 将上述混在一起的分支抽离到不同函数中处理,取消通过
CBD
类型选择解析类型的过程; - 丢弃
void*
指针,在「调用签名」中使用强类型指针可以避免void*
指针向具体数据交换类性指针的转换; 强类型指针,通过使用智能指针避免手动管理内存,丢弃原生指针;(智能指针的常量引用,不要拷贝)实际业务分析,事实上应该使用顺序容器,vector
即可- 很大程度上减少
CBD
枚举类型的存在感,甚至丢弃; - 数据交换类(
Dyna
/Tick
/stNews
等)取消强类型成员变量,对外只提供接口。进一步降低文件之间的依赖关系。
上述优点的核心就是:编码的时候,展现层最低程度地依赖网络层,尤其是「约定」什么的最烂了。
无法判断相等
两个 std::function
无法做比较:大于、小于和相等。
只能判断某个 std::function
是否包含目标,和 nullptr
的相等比较已经在 C++20 中移除。
传值还是引用
传引用潜在威胁严重:参考 https://github.com/tnie/YD-function/tree/check_ref 分支
As with any other type, it’s safe to store a reference only if you have some other way to guarantee that the referred object is still valid whenever that reference is used. 摘抄来源
“重载了函数运算符的类”的对象,调用由此赋值产生的 function<>
对象时,操作的是前者的拷贝,而非本身。——此问题源于构造 function<>
对象,而非 function<>
对象传参。正确做法是:
1 | auto multt3 = Multt3(); |
结合 stackoverflow 网站多个提问,发现还是以传值为主,基本没有传引用的场景。但我还是用了引用,而且是常量引用!
仔细查看 std::function 的手册,会发现 std::function::operator()
函数是带 const
修饰的,这也意味着使用 const std::function<int(int)>&
传常量引用是完全可以的,无需担心修改源对象非法(指通过 std::bind 绑定的对象或者重载了函数调用运算符的对象),置于 Why is the std::function () operator const? 抽空了解一下,对此感到困惑还是源于一直未弄懂 function
类模板 到底是对象呢,还是指针?
回调时的合法性
使用“重载了函数调用符号”的类、std::bind(&Class::Function, ObjectPointer, std::placeholder::_1)
等涉及类的可调用类型时,因为跨内存模块(动态库)、跨线程,当对象析构后执行回调如何处理?
通俗地讲,在调用类的普通成员函数时如何判断所属对象已经析构,不然当成员函数使用成员变量时直接造成程序崩溃。
网络层是否不应该负责(主要是做不到)对展现层传入的类模板 std::function<>
对象做深度的有效性判断?毕竟展现层传入的 raw 究竟是函数指针、lambda
表达式还是 std::bind()
返回值,数据层都一无所知
网络层也就只能检查 std::function<>
对象
是否存储可调用函数对象
c
至于对象 c
的有效性——比如通过 std::bind
绑定对象 t
时,t
是否析构——网络层是无法检查的
所以,展现层应该自检。可能的一种实现方式是:
1 | /*网络层接口*****************************/ |
上述 EXAMPLE
类,因为回调时对象可能已经析构,所以回调函数 callback1()
使用类的成员并不合适(虽然使用 wptr
判断了),且 bind()
时 this
, wptr
也有重复( wptr
主要是为了判断对象是否还活着, this
无现实意义)。改进 EXAMPLE
类:
1 | /*展现层可能的实现方式*************************************/ |