搞定c++11新特性std::function和lambda表达式

发表于 3年以前  | 总阅读数:1387 次

c++11新增了std::function、std::bind、lambda表达式等封装使函数调用更加方便。

std::function

讲std::function前首先需要了解下什么是可调用对象

满足以下条件之一就可称为可调用对象:

  • 是一个函数指针
  • 是一个具有operator()成员函数的类对象(传说中的仿函数),lambda表达式
  • 是一个可被转换为函数指针的类对象
  • 是一个类成员(函数)指针
  • bind表达式或其它函数对象

而std::function就是上面这种可调用对象的封装器,可以把std::function看做一个函数对象,用于表示函数这个抽象概念。std::function的实例可以存储、复制和调用任何可调用对象,存储的可调用对象称为std::function的目标,若std::function不含目标,则称它为空,调用空的std::function的目标会抛出std::bad_function_call异常。

使用参考如下实例代码:

std::function<void(int)> f; // 这里表示function的对象f的参数是int,返回值是void
#include <functional>
#include <iostream>

struct Foo {
   Foo(int num) : num_(num) {}
   void print_add(int i) const { std::cout << num_ + i << '\n'; }
   int num_;
};

void print_num(int i) { std::cout << i << '\n'; }

struct PrintNum {
   void operator()(int i) const { std::cout << i << '\n'; }
};

int main() {
   // 存储自由函数
   std::function<void(int)> f_display = print_num;
   f_display(-9);

   // 存储 lambda
   std::function<void()> f_display_42 = []() { print_num(42); };
   f_display_42();

   // 存储到 std::bind 调用的结果
   std::function<void()> f_display_31337 = std::bind(print_num, 31337);
   f_display_31337();

   // 存储到成员函数的调用
   std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
   const Foo foo(314159);
   f_add_display(foo, 1);
   f_add_display(314159, 1);

   // 存储到数据成员访问器的调用
   std::function<int(Foo const&)> f_num = &Foo::num_;
   std::cout << "num_: " << f_num(foo) << '\n';

   // 存储到成员函数及对象的调用
   using std::placeholders::_1;
   std::function<void(int)> f_add_display2 = std::bind(&Foo::print_add, foo, _1);
   f_add_display2(2);

   // 存储到成员函数和对象指针的调用
   std::function<void(int)> f_add_display3 = std::bind(&Foo::print_add, &foo, _1);
   f_add_display3(3);

   // 存储到函数对象的调用
   std::function<void(int)> f_display_obj = PrintNum();
   f_display_obj(18);
}

从上面可以看到std::function的使用方法,当给std::function填入合适的参数表和返回值后,它就变成了可以容纳所有这一类调用方式的函数封装器。std::function还可以用作回调函数,或者在C++里如果需要使用回调那就一定要使用std::function,特别方便,这方面的使用方式大家可以读下我之前写的关于线程池和定时器相关的文章。

std::bind

使用std::bind可以将可调用对象和参数一起绑定,绑定后的结果使用std::function进行保存,并延迟调用到任何我们需要的时候。

std::bind通常有两大作用:

  • 将可调用对象与参数一起绑定为另一个std::function供调用
  • 将n元可调用对象转成m(m < n)元可调用对象,绑定一部分参数,这里需要使用std::placeholders

具体示例:

#include <functional>
#include <iostream>
#include <memory>

void f(int n1, int n2, int n3, const int& n4, int n5) {
   std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << std::endl;
}

int g(int n1) { return n1; }

struct Foo {
   void print_sum(int n1, int n2) { std::cout << n1 + n2 << std::endl; }
   int data = 10;
};

int main() {
   using namespace std::placeholders;  // 针对 _1, _2, _3...

   // 演示参数重排序和按引用传递
   int n = 7;
   // ( _1 与 _2 来自 std::placeholders ,并表示将来会传递给 f1 的参数)
   auto f1 = std::bind(f, _2, 42, _1, std::cref(n), n);
   n = 10;
   f1(1, 2, 1001);  // 1 为 _1 所绑定, 2 为 _2 所绑定,不使用 1001
                    // 进行到 f(2, 42, 1, n, 7) 的调用

   // 嵌套 bind 子表达式共享占位符
   auto f2 = std::bind(f, _3, std::bind(g, _3), _3, 4, 5);
   f2(10, 11, 12);  // 进行到 f(12, g(12), 12, 4, 5); 的调用

   // 绑定指向成员函数指针
   Foo foo;
   auto f3 = std::bind(&Foo::print_sum, &foo, 95, _1);
   f3(5);

   // 绑定指向数据成员指针
   auto f4 = std::bind(&Foo::data, _1);
   std::cout << f4(foo) << std::endl;

   // 智能指针亦能用于调用被引用对象的成员
   std::cout << f4(std::make_shared<Foo>(foo)) << std::endl;
}

lambda表达式

lambda表达式可以说是c++11引用的最重要的特性之一,它定义了一个匿名函数,可以捕获一定范围的变量在函数内部使用,一般有如下语法形式:

auto func = [capture] (params) opt -> ret { func_body; };

其中func是可以当作lambda表达式的名字,作为一个函数使用,capture是捕获列表,params是参数表,opt是函数选项(mutable之类), ret是返回值类型,func_body是函数体。

一个完整的lambda表达式:

auto func1 = [](int a) -> int { return a + 1; };
auto func2 = [](int a) { return a + 2; };
cout << func1(1) << " " << func2(2) << endl;

如上代码,很多时候lambda表达式返回值是很明显的,c++11允许省略表达式的返回值定义。

lambda表达式允许捕获一定范围内的变量:

  • []不捕获任何变量
  • [&]引用捕获,捕获外部作用域所有变量,在函数体内当作引用使用
  • [=]值捕获,捕获外部作用域所有变量,在函数内内有个副本使用
  • [=, &a]值捕获外部作用域所有变量,按引用捕获a变量
  • [a]只值捕获a变量,不捕获其它变量
  • [this]捕获当前类中的this指针

lambda表达式示例代码:

int a = 0;
auto f1 = [=](){ return a; }; // 值捕获a
cout << f1() << endl;

auto f2 = [=]() { return a++; }; // 修改按值捕获的外部变量,error
auto f3 = [=]() mutable { return a++; };

代码中的f2是编译不过的,因为我们修改了按值捕获的外部变量,其实lambda表达式就相当于是一个仿函数,仿函数是一个有operator()成员函数的类对象,这个operator()默认是const的,所以不能修改成员变量,而加了mutable,就是去掉const属性。

还可以使用lambda表达式自定义stl的规则,例如自定义sort排序规则:

struct A {
   int a;
   int b;
};

int main() {
   vector<A> vec;
   std::sort(vec.begin(), vec.end(), [](const A &left, const A &right) { return left.a < right.a; });
}

总结

std::function和std::bind使得我们平时编程过程中封装函数更加的方便,而lambda表达式将这种方便发挥到了极致,可以在需要的时间就地定义匿名函数,不再需要定义类或者函数等,在自定义STL规则时候也非常方便,让代码更简洁,更灵活,提高开发效率。

参考资料

https://zh.cppreference.com/w/cpp/utility/functional/bind

https://zh.cppreference.com/w/cpp/utility/functional/function

https://zh.cppreference.com/w/cpp/language/lambda

https://zh.cppreference.com/w/cpp/named_req/Callable

https://blog.csdn.net/m_buddy/article/details/74389409

《深入应用c++11:代码优化与工程级应用》

《Effective Modern C++》

 相关推荐

丽珠集团回应“涉及违规支出公关费用” :内部正在调查,费用占比是行业正常水平

8月7日消息,针对股价大跌和网传“涉及违规支出公关费用”的相关消息,丽珠集团公司认为今日的大跌属于近期医药行业反腐风暴逐渐加强,投资者情绪变化所致。

发布于:1月以前  |  262次阅读  |  详细内容 »

美国芯片巨头英特尔与中方合作!将在深圳新建芯片创新中心

据香港《南华早报》7月31日报道,英特尔与深圳合作,建立新的芯片创新中心以加深关系。

发布于:1月以前  |  233次阅读  |  详细内容 »

工信部要求开展 App 备案,微信小程序完成备案后才可上架

近日,工业和信息化部印发了《工业和信息化部关于开展移动互联网应用程序备案工作的通知》。

发布于:1月以前  |  213次阅读  |  详细内容 »

微信要做“小绿书”?知情人士:小范围内测,优化视频号图文发布及呈现

据网友爆料,微信正在灰度测试“小绿书”。从知情人士处了解到,这是一次非常小范围的内测,不是新功能,初衷就是为了更方便视频号创作者发布图文短内容,以及提高用户获得信息的效率。据知情人士介绍,视频号本身就可以发图片,这次内测只是把发布图片单独设计了一个按钮,在“看一看”那里可以集中查看而已。

发布于:1月以前  |  212次阅读  |  详细内容 »

X、马斯克寻求驳回有关前推特员工遣散费的诉讼

8月2日消息,社交媒体公司 X Corp周一向特拉华州威尔明顿联邦法院提交的一份文件中称,今年5月起诉该公司的六名前员工并非推特与马斯克旗下控股公司于2022年达成的合并协议的当事方,因此他们不能以涉嫌违反该协议为由起诉他们。

发布于:1月以前  |  210次阅读  |  详细内容 »

家乐福被裁员工曝未拿到补偿金,此前通知补偿金打6折一次性发和全款分12期之间二选一

据报道,近期,不少前家乐福员工爆料称,此前陆续接到裁员通知,给出的方案基本都是N的补偿费,但不少人却迟迟未拿到款项。据悉,被裁员工的工龄有长短,陆续都是在数月内被裁,补偿金在打6折一次性拿和全款分12期之间二选一。

发布于:1月以前  |  205次阅读  |  详细内容 »

OpenAI已注册GPT5商标

8月1日消息,OpenAI 今年 3 月推出了 GPT-4,成为“AI 模型”的代名词,甚至一度引发了业界恐慌,此前报道,以马斯克为首的科技圈人物要求“OpenAI 暂时停止研究 GPT-5 研发”。

发布于:1月以前  |  202次阅读  |  详细内容 »

特斯拉收购德国无线充电技术公司,未来或将投放相关产品

6月份关于特斯拉的一则收购传闻近日得到证实。当地时间7月31日,特斯拉确认已完成对德国无线充电技术公司Wiferion的计划收购,同时该公司更名为Tesla Engineering Germany GmbH。

发布于:1月以前  |  196次阅读  |  详细内容 »

全球勒索软件攻击创历史新高,美国是全球勒索软件攻击首要目标

Malwarebytes 的报告显示,全球勒索软件攻击呈现上升趋势。

发布于:1月以前  |  194次阅读  |  详细内容 »

美交管局对特斯拉部分车型展开调查,特斯拉部分车型被曝转向失灵

据报道,美国国家公路交通安全管理局8月1日表示,已对28万辆特斯拉Model 3和Model Y电动汽车展开调查,原因是这些车辆被曝存在转向失灵的情况。

发布于:1月以前  |  193次阅读  |  详细内容 »

时隔两年再战社交,腾讯内测「M8」APP

腾讯内部正在研发一款名为“M8”的社交APP,由QQ团队打造,定位是基于地图的虚拟社交产品,主攻年轻人社交赛道。据消息人士透露,产品或于8月份对外开启测试。

发布于:1月以前  |  189次阅读  |  详细内容 »

听歌时被银行AI骚扰电话打断,他起诉“索赔”110万元!

“在此事发生的过程中,本人正通过歌曲进行能力的激发,歌曲被打断,非但原有的效果并未达到,更让我承受了巨大的心理压力。”近日,一份题为“精神损害赔偿起诉状”的文字在社交平台上引发关注。市民辛先生在起诉状中表示,除了请法院判处被告向原告赔偿精神损失费10万元以外,还要求附带赔偿大卫·鲍伊演出名誉损失费100...

发布于:1月以前  |  187次阅读  |  详细内容 »

外媒:印度要求特斯拉效仿苹果,让其中国供应商与印企合资后再落地

据外媒8月1日报道,三位印度政府消息人士称,在特斯拉研究在印建厂之际,印度官员要求特斯拉效仿苹果公司,寻找印度当地公司与中国供应商合作。

发布于:1月以前  |  186次阅读  |  详细内容 »

世界首例自动驾驶致命车祸司机认罪:判处三年缓刑

8月1日消息,据报道,世界首起涉及自动驾驶汽车的致命碰撞事故日前进行了最终宣判,Uber自动驾驶汽车的安全员认罪并被判处三年缓刑。

发布于:1月以前  |  182次阅读  |  详细内容 »

全新声学攻击AI模型:从键盘敲击声窃取数据,准确率高达95%

近日,来自英国大学的一个研究团队训练了一种深度学习模型,该模型可以从使用麦克风记录的键盘敲击声音中,窃取数据,准确率高达95%。据称,此类攻击会严重影响目标的数据安全,它可能会将人们的密码、讨论、消息或其他敏感信息泄露给恶意第三方。

发布于:1月以前  |  180次阅读  |  详细内容 »

推特在苹果商店更名为X,X公司获得苹果首家单字符批准

7月31日,Twitter 应用在苹果 App Store 已经更名为 X,这意味着该公司可能获得了苹果的特批,因为此前 App Store 明确规定应用名称至少需要两个字母。

发布于:1月以前  |  180次阅读  |  详细内容 »

已拿下两家订单,零跑造车八年后开始技术输出

在7月31日举办的零跑汽车全新电子电气架构发布会上,零跑科技创始人、董事长、CEO 朱江明向现场媒体表示:" 零跑不仅要做一家汽车公司,还要做一家技术输出的公司。

发布于:1月以前  |  178次阅读  |  详细内容 »

小鹏汽车宣布严打占桩:超时占位每分钟收费1元

8日消息,小鹏汽车在其官方 App 上发布公告,指出公司将严打充电桩 “ 霸位 ” 行为。公告表示,近期发现一些车辆在充电站占桩不充电,或插枪不充电,浪费了宝贵的充电桩资源。为了解决这个问题,小鹏汽车已经加强了场站管理,全方位布局监控,检测场站情况,并增加了巡检次数。

发布于:1月以前  |  174次阅读  |  详细内容 »

蚂蚁集团以 6.28 亿美元出售印度“支付宝” Paytm 10.3% 股份,不再是其最大股东

8月7日消息,据外媒报道,蚂蚁集团旗下的一家子公司将出售印度最大的支付平台 Paytm10.3% 的股份,而 Paytm 的创始人兼 CEO Vijay Shekhar Sharma 将增持该股份,以简化该公司的所有权结构。消息公布后,Paytm 股价一度上涨 11.4%,今年以来该公司股价已经上涨了超过 50%。

发布于:1月以前  |  171次阅读  |  详细内容 »

周鸿祎:希望超级人工智能出来之后,先解决常温超导等问题而不是娱乐

8月8日消息,近日,周鸿祎在社交平台发文谈温室超导问题。他表示,最近室温超导比较火,室温超导技术的实现将对能源、电子、医疗、交通、AI等领域产生深远的影响。

发布于:1月以前  |  170次阅读  |  详细内容 »
 相关文章
 目录