以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/gt_zxMwhu8UixzCMF73Dng
C++ 原生支持函数输入参数的默认值,但是有些业务场景下对原有设定的默认值不满意,那么可不可以临时改改?
请注意,随意改变原有代码逻辑,并不符合软件设计原则。有没有什么方案或者特性可以做到不修改原有代码逻辑,以实现新增参数默认值(或者在调用时绑定特性参数)的功能?
从 C++11 开始,标准库提供了 std::bind 用于绑定函数 f 和调用参数,返回一个新可调用函数对象 fn (也有喊它做仿函数的)。于是,在调用函数对象 fn 和输入新参数时,相当于调用了原来的函数 f 并自动按照绑定的关系映射参数,映射的方式非常灵活。这样被绑定映射的参数,实现原有函数 f 的参数新默认值功能,或者减少调用函数 f 时输入的参数个数等便捷需求。
简而言之,std::bind 被称为转发调用包装器。
绑定时,通过占位符 _1, _2, _3… ,指定在调用函数对象 fn 的输入参数和传递给函数 f 的参数之间的映射位置。
被绑定的函数 f 也分种类,那么,基于不同类型函数的应用示例是怎样的呢?
#include <functional>
void output1(int a)
{
printf("%d\n", a);
}
void output2(int a, int b)
{
printf("%d %d\n", a, b);
}
int main(int argc, char * argv[])
{
auto fn1 = std::bind(output1, std::placeholders::_1);
fn1(1);
auto fn2 = std::bind(output2, std::placeholders::_2, 0);
fn2(3, 2);
return 0;
}
output:
1
2 0
占位符代表函数对象的输入参数,std::placeholders::_1 对应调用函数对象 fn 时的第 1 个输入参数,依次类推。
细心的你可能会留意 std::bind 返回的是可调用对象,这个和同样从 C++11 开始引入的 Lambda 表达式概念是类似的,那么有没有可能用 Lambda 表达式互相替换?试试改为使用 Lambda 表达式实现
int main(int argc, char * argv[])
{
auto fn1 = [](int a) -> void {
output1(a);
};
fn1(1);
auto fn2 = [](int a, int b) -> void {
output2(b, 0);
};
fn2(3, 2);
return 0;
}
这样子替换后,执行输出结果是一样的。
output:
1
2 0
如果被绑定的函数是对象成员,需要指定所属对象,看代码
#include <functional>
class A
{
public:
void output(int a, int b)
{
printf("%d %d\n", a, b);
}
};
int main(int argc, char * argv[])
{
A obj;
auto fn = std::bind(&A::output, &obj, std::placeholders::_2, std::placeholders::_1);
fn(5, 3);
return 0;
}
output:
3 5
用 Lambda 表达式替换试试
int main(int argc, char * argv[])
{
A obj;
auto fn = [&obj](int a, int b) -> void {
obj.output(b, a);
};
fn(5, 3);
return 0;
}
output:
3 5
Notice:绑定的时候需要指定上下文环境(实例对象),这种场景已经涉及到闭包的概念了。由于上下文环境是有生命周期的,比如上面用到的实例对象 a,如果在超出了其生命周期的情况下,引用其内部已被释放的资源,是会引起程序执行异常的,这里提醒一下。
以上面两种场景示例代码的比较,在参数绑定的场景下,为了达到相同效果,Lambda 表达式的书写略显罗嗦,而 std::bind 的格式更有函数式编程的味道,可见,某些场景下推荐 std::bind 替代 Lambda 表达式。
能不能平替也分场景,三思而行。