Function Objects 相关问题记录
本文最后更新于:2023年12月17日 下午
前言
此前在学习简单线程池时用到了 std::bind
,所以也学习了相关内容,其中也遇到了一些问题,特此记录一下。原本是打算放在“线程池”那篇文章里作为一个小章节,后来想想这里遇到的问题与原文关系并不是很密切,所以还是单独拆分出来写个小文章。
Function Objects
Function objects 的定义概念可以直接参考 https://en.cppreference.com/w/cpp/utility/functional,简单翻译一下,就是可以调用 operator() 的对象被视作是 function objects,而相关联的函数调用INVOKE
文档上也给出了详细说明。
这里简单总结一下,就是函数调用操作可以视为 INVOKE(f, t1, t2, ... , tN)
,可以总结为如下三种情况:
- 如果
f
是一个指向T
类成员函数的指针,那么会根据t1
的类型不同(类型为T或是T的派生类,对象引用,对象指针),采取对应的函数调用方式(注意,这里的指针也可以是智能指针) - 如果
f
是一个指向T
类的成员变量,并且N
为1,则等同于要去访问这个类的成员变量(也会根据t1
类型做出适配的调用) - 如果不是上述两种情况,则等同于
f(t1, t2, ..., tN)
,进行一次函数调用,这里的f
就是一个 FunctionObject
问题记录
然后在看 mem_fn , bind, function 相关实例代码时,对一些传参方式很疑惑,通过用不同的方式来传入对象、指针、引用,会有不同的输出表现,具体示例代码和输出结果如下所示,也可以直接看这个链接 https://compiler-explorer.com/z/n81o8bssP 。
1 |
|
输出结果:
1 |
|
那么根据输出结果不难发现如下几个现象:
- 对于 mem_fn 也就是代码里的 f1 ,无论传入的是 ff 还是 &ff ,执行后都是能正常修改 ff 对象中的 num_ 值。
- 对于 bind,如果提前绑定了 ff,则执行后并没有修改 ff 对象中的 num_ 值;而如果使用 placeholder 或者 传入 &ff ,则也可以正常修改。
- 对于 function,如果指定第一个参数为 Foo,此时传入 ff,执行后也没有修改 ff 对象中的 num_ 值。
所以我当时就挺困惑的,为什么会有上述的差异,对应的原理又是什么?后来也是查阅了一天,并最终在 StackOverflow 上提问后,得到了解答,具体可以看这个链接 StackOverflow 提问 。
分析思考
简单来说,相似的传参方式却得到了不同的结果,如代码里的 f1(ff,1)
和 f7(ff,1)
,这其实是由于传参时一个是 pass by reference,一个是 pass by value 导致的。下面会进一步地对上述三点进行分析。
- 对于
mem_fn
的表现,也就是f1
和f2
,他们传值方式都是 pass by reference,这一点可以在 https://en.cppreference.com/w/cpp/utility/functional/mem_fn 得到证实,可以看到operator()(Arg&&... args)
形参格式为Args&&
引用格式,然后进行完美转发再执行函数调用,那么此时就又可以联想之前提到的INVOKE
调用规则。因为是引用,所以传入ff
对象后执行调用能够直接修改对象,传入&ff
调用也是修改ff
对象本身。
- 对于
std::bind
,返回一个 function object,对该 object 传入的参数都是拷贝或者移动,除非使用std::ref
或者std::cref
表示传入引用,所以传入ff
或者&ff
时都是进行拷贝,在真正INVOKE
时f3
不能修改ff
对象,f4
可以。当但使用placeholders
时,可以看到对应说明,会将对应传入的参数转发,转变成 && 引用类型,因此这是传入是个引用,所以f2
的调用能够修改ff
对象。
对于 std::bind 参数的拷贝,还值得注意的是,在 bind 的时候拷贝的是实参,在真正调用执行时才去拷贝形参,所以当拷贝指针的值时候需要特别注意对应的生命周期。
1 |
|
- 对于
std::function
,可以看到对应的operator()
接受参数的类型是Args...
而不是 mem_fn 的Args&&...
,注意到这两者细微的差别了么?这里其实就隐含说明传入的参数都是拷贝传入的,所以f7
执行后并不会影响ff
对象。
总结
此次遇到的问题,简单来说,就是对于某个结果现象不理解,或者说是不符合预期而产生疑惑,但其实从这个现象,再结合一下C++知识,不难想到一个是值传递一个是引用传递,但我当时就是没有找到对应的佐证,来说明为什么这里是值传递或是引用传递。后来也是仔细翻阅了 cppreference 上的说明,才看到证据,不过限于我个人能力,可能上述的分析也仅仅是我个人的推测,自身也没有很透彻地理解,所以如果哪里理解有误,还请联系我进行改正。