C++可调用Callable类型的总结

转自:高性能架构探索
自从在使用 std::thread 构造函数过程中遇到了 callable 类型的概念以来用到了很多关于它的使用.
因此本文把使用/调查结果总结出来. 包括 callable 的基础概念, 典型的 callable 类型介绍.
例如函数对象(狭义), 函数指针, lambda 匿名函数, 函数适配器, std::function 仿函数等.
callable 类型
基础
• 定义(参考):可调用(callable) 类型是可应用 invoke 操作(std::invoke 是在 c++17 里定义的类, 感觉意思就是执行函数操作的模板类.)
• 要求:一个 t 类型要满足为 callable 需要以下表达式在不求值语境中良构.invoke(f, [std::declval]argtypes>()...) 即 invoke(f, t1, t2, ..., tn).其中 f 为 t 类型的对象, argtypes 为适合的实参类型列表, r 为适合的返回类型.r为 void 的时可以表示为 static_cast(invoke(f, t1, t2, ..., tn)).
• 详细地
1. 若 f 是类 t 的成员函数指针: 上面等价于 (t1.*f)(t2, ..., tn) 或者 t1 是指针时 ((*t1).*f)(t2, ..., tn).
2. 若 n == 1 且 f 是类 t 的数据成员指针: invoke(f, t1) 等价于 t1.*f, 或者指针形式 (*t1).*f.
3. 均不满足上面的情况表明 f 是一个函数对象(function object) : invoke(f, t1, t2, ..., tn) 等价于 f(t1, t2, ..., tn).
同时, 对于成员函数指针和数据成员指针, t1 可以是一个常规指针或一个重载了 operator* 的类的对象, 例如智能指针 std::unique_ptr 或 std::shared_ptr.
可作为参数的标准库
下列标准库设施接受任何可调用(callable)类型:
库 说明
function(c++11) 包装具有指定函数调用签名的任意_可复制构造类型_的可调用对象 (类模板)
bind(c++11) 绑定一或多个实参到函数对象 (函数模板)
reference_wrapper(c++11) 可复制构造 (copyconstructible)且可复制赋值 (copyassignable)的引用包装器 (类模板)
result_of (c++11)(c++20 中移除) invoke_result(c++17) 推导以一组实参调用一个可调用对象的结果类型 (类模板)
thread (构造函数) 构造新的 thread 对象 (std::thread 的公开成员函数)
call_once(c++11) 仅调用函数一次, 即使从多个线程调用 (函数模板)
async(c++11) 异步运行一个函数(有可能在新线程中执行),并返回保有其结果的 std::future(函数模板)
packaged_task(c++11) 打包一个函数, 存储其返回值以进行异步获取 (类模板)
一些典型的 callable 类型
函数对象 function object
一个重载了括号操作符()的对象, 也就是可以以f(args)形式进行函数调用的对象.
#include#includeusing namespace std;class add {public:    const int operator()(const int a,const int b){        return a+b;}};int main() {    add addfunction; //函数对象    cout< 对于普通函数来说, 只要签名一致, 其类型就是相同的, 是类型不安全的. 但是这并不适用于函数对象, 因为函数对象的类型是其类的类型. 这样, 函数对象有自己的类型, 这也意味着函数对象可以用于模板参数, 这对泛型编程有很大提升. 因为函数对象一般用于模板参数, 模板一般会在编译时会做一些优化. 因此函数对象一般快于普通函数. 类也可以在使用的时候动态再产生, 节省成本.
既然是类, 那就有它的限制, 例如要注意, 如同其他所有对象(狭义上的对象, 我感觉内置类型其实也可以被叫对象, 按场景区分吧)一样, 如果 pass-by-value 的化, 对象里的成员变量是被复制进去的, 一旦对象被析构了, 里面的成员变量也是无法保存下来的. 所以可以 pass-by-reference/pointer.
函数指针并不是没有其用处了, 对于 c api 库里的某些函数不支持函数对象还是有用武之地的. 例如  里面的排序函数 qsort 只能调用函数指针.
void qsort( void *ptr, size_t count, size_t size,int (*comp)(const void *, const void *) );  
函数
除了普通的函数, 当然也包括类成员函数.
这里不提及模板函数, 因为模板函数的概念只存在于编译期, 运行期的函数没有模板的概念, 都是经过完全特化过的, 因此与普通函数/类成员函数的概念是一致的.
函数指针
#include#includeusing namespace std;int addfunc(int a, int b)  {      return a + b;}  int main() {    int (*add1) (int a, int b); //函数指针,函数名两侧的()不可省略    int (*add2) (int a, int b);    add1 = &addfunc;    add2 = addfunc;           cout << (*add1) (3, 2)< 各项具体含义如下
1. capture list: 捕获外部变量列表.
2. params list: 形参列表.
3. mutable指示符: 用来说用是否可以修改捕获的变量, 因为lambda的() operator() 默认是 const 的.
4. exception: 异常设定.
5. return type: 返回类型, 允许省略 lambda 表达式的返回值定义.
6. function body: 函数体.
捕获形式:
捕获形式 说明
[] 不捕获任何外部变量
[变量名, …] 默认以值得形式捕获指定的多个外部变量(用逗号分隔), 如果引用捕获, 需要显示声明(使用 & 说明符)
[this] 以值的形式捕获 this 指针
[=] 以值的形式捕获所有外部变量
[&] 以引用形式捕获所有外部变量
[=, &x] 变量x以引用形式捕获,其余变量以传值形式捕获
[&, x] 变量x以值的形式捕获,其余变量以引用形式捕获
省略其中的某些成分来声明”不完整”的lambda表达式:
序号 格式
1 [capture list] (params list) -> return type {function body}
2 [capture list] (params list) {function body}
3 [capture list] {function body}
一些关于 lambda 表达式的细节
1. 延迟调用
按值捕获与按引用捕获的区别.
int a = 0;auto f = [=]{ return a; };      // 按值捕获外部变量a += 1;                         // a被修改了std::cout << f() < bool { return a < b; });  
c++14 中的 lambda 新特性
1. lambda 捕捉表达式/右值
// 利用表达式捕获,可以更灵活地处理作用域内的变量int x = 4;auto y = [&r = x, x = x + 1] { r += 2; return x * x; }();// 此时 x 更新为6,y 为25// 直接用字面值初始化变量auto z = [str = string]{ return str; }();// 此时z是const char* 类型,存储字符串 string//不能复制只能移动的对象,可以用std::move初始化变量auto mypi = std::make_unique(3.1415);auto circle_area = [pi = std::move(mypi)](double r) { return *pi * r * r; };cout << circle_area(1.0) << endl; // 3.1415  
2. 泛型 lambda 表达式:
auto add = [](auto x, auto y) { return x + y; };//推断类型int x = add(2, 3);   // 5double y = add(2.5, 3.5);  // 6.0  
函数适配器
将函数对象与其它函数对象, 或者特定的值, 或者特定的函数相互组合的产物. 由于组合特性, 函数适配器可以满足特定的需求, 头文件  定义了几种函数适配器:
std::bind(op, args...): 将函数对象 op 的参数绑定到特定的值 args.
std::mem_fn(op): 将类的成员函数转化为一个函数对象.
std::not1(op), std::not2(op),std::unary_negate,std::binary_negate: 一元取反器和二元取反器.
std::bind
这里的函数对象就包括了上面所有的类型, 当然也包含自己, 因此可以利用 std::bind 封装出很多有意思的功能.
下面的例子来自于分享.
• 嵌套
// 定义一个接收一个参数,然后将参数加10再乘以2的函数对象auto plus10times2 = std::bind(std::multiplies{},        std::bind(std::plus{}, std::_1, 10), 2);cout << plus10times2(4) << endl; // 输出:  28 // 定义3次方函数对象auto pow3 = std::bind(std::multiplies{},        std::bind(std::multiplies{}, std::_1, std::_1),        std::_1);cout << pow3(3) << endl;  // 输出: 27  
• 调用类中的成员函数
class person{public:    person(const string& n) : name{ n } {}    void print() const { cout << name << endl; }    void print2(const string& prefix) { cout << prefix << name << endl; }private:    string name;};int main(){    vector p{ person{tick}, person{trick} };    // 调用成员函数print    std::for_each(p.begin(), p.end(), std::bind(&person::print, std::_1));    // 此处的std::_1表示要调用的person对象,所以相当于调用arg1.print()    // 输出: tick   trick    std::for_each(p.begin(), p.end(), std::bind(&person::print2, std::_1,        person: ));    // 此处的std::_1表示要调用的person对象,所以相当于调用arg1.print2(person: )    // 输出: person: tick   person: trick    return 0;}  
• 调用 lambda 表达式
vector data{ 1, 2, 3, 4 };auto func = std::bind([](const vector& data) { cout << data.size() << endl; },                        std::move(data));func();  // 4cout << data.size() <= 'a' && c <= 'z')        return static_cast(c - 'a' + 'a');    return c;}int main(){    string s{ internationalization };    string sub{ nation };    auto pos = std::search(s.begin(), s.end(), sub.begin(), sub.end(),                        std::bind(std::equal_to{},                             std::bind(mytoupper, std::_1),                            std::bind(mytoupper, std::_2)));    if (pos != s.end()){        cout << sub <<  is part of  << s << endl;    }    // 输出: nation is part of internationalization    return 0;}  
• 默认 pass-by-value, 如果想要 pass-by-reference, 需要用 std::ref 和 std::cref 包装.
std::cref 比 std::ref 增加 const 属性.
void f(int& n1, int& n2, const int& n3){    cout << in function:  << n1 << ' ' << n2 << ' ' << n3 << '';    ++n1;    ++n2;    // ++n3;  //无法编译}int main(){    int n1 = 1, n2 = 2, n3 = 3;    auto boundf = std::bind(f, n1, std::ref(n2), std::cref(n3));    n1 = 10;    n2 = 11;    n3 = 12;    cout << before function:  << n1 << ' ' << n2 << ' ' << n3 << '';    boundf();    cout << after function:  << n1 << ' ' << n2 << ' ' << n3 << '';    //  before function : 10 11 12    //  in function : 1 11 12    //  after function : 10 12 12    return 0;}  
std::mem_fn
与 std::bind 相比, std::mem_fn 的范围又要小一些, 仅调用成员函数, 并且可以省略掉用于调用对象的占位符.
因此使用 std::men_fn 不需要绑定参数, 可以更方便地调用成员函数.
vector p{ person{ tick }, person{ trick } };std::for_each(p.begin(), p.end(), std::mem_fn(&person::print));// 输出: trick trickperson n{ bob };std::mem_fn(&person::print2)(n, person: );// 输出: person: bob  
std::mem_fn 还可以调用成员变量
class foo{public:    int data = 7;    void display_greeting() { cout << hello, world.; }    void display_number(int i) { cout << number:  << i << ''; }    };int main(){    foo f;    // 调用成员函数    std::mem_fn(&foo::display_greeting)(f);  // hello, world.    std::mem_fn(&foo::display_number)(f, 20);  // number: 20    // 调用数据成员    cout << std::mem_fn(&foo::data)(f) << endl;  // 7    return 0;}  
std::not1 、std::not2、std::unary_negate、std::binary_negate
std::not1, std::not2 分别构造一个与谓词结果相反的一元/二元函数对象.
std::unary_negate, std::binary_negate 分别返回其所保有的一元/二元谓词的逻辑补的包装函数对象, 其对象一般为 std::not1, std::not2 构造的函数对象,即又加了一层包装.
下面分别是其使用示例:
//std::not1#include #include #include int main(int argc, char **argv) {      std::vector nums = {5, 3, 4, 9, 1, 7, 6, 2, 8};    std::function less_than_5 = [](int x){ return x <= 5; };    // count numbers of integer that not less and equal than 5    std::cout << std::count_if(nums.begin(), nums.end(), std::not1(less_than_5)) << ;    //输出结果4    return 0;}//std::not2using namespace std;int main(int argc, char **argv) {      std::vector nums = {5, 3, 4, 9, 1, 7, 6, 2, 8};    std::function ascendingorder = [](int a, int b) { return a std::not_fn
注意 c++17 已经把上面的 std::not1, std::not2, std::unary_negate 和 std::binary_negate 抛弃, 统一由 std::not_fn 替代.
//移除把满足谓词p的元素都copy到容器中template auto filterremovecopyif(const std::vector& vec, pred p) {    std::vector out;    std::remove_copy_if(begin(vec), end(vec),                         std::back_inserter(out), std::not_fn(p));    return out;}  
std::function
五花八门的 callable, 个个都是人才, 但是不好带(不好实现 generic programming), 所以一个把所有 callable 对象封装成统一形式的类型模板.
std::function 的实例可以对任何可以调用的目标实体进行存储, 复制, 和调用操作, 实现一种类型安全的包裹.
基础介绍
原型为:
template //r是返回值类型,args是函数的参数类型class function;  
其存储的可调用对象被称为 std::function 的目标. 若 std::function 不含目标, 则称它为空. 调用空 std::function 的目标导致抛出 std::bad_function_call 异常.
std::function 满足可复制构造 (copy constructible) 和可复制赋值 (copy assignable) (参考).
瑞士军刀一般的功能, 代码例子如下:
#include #include  struct foo {    foo(int num) : num_(num) {}    void print_add(int i) const { std::cout << num_+i << ''; }    int num_;}; void print_num(int i){    std::cout << i << '';} struct printnum {    void operator()(int i) const    {        std::cout << i << '';    }}; int main(){    // 存储自由函数    std::function f_display = print_num;    f_display(-9);     // 存储 lambda    std::function f_display_42 = []() { print_num(42); };    f_display_42();     // 存储到 std::bind 调用的结果    std::function f_display_31337 = std::bind(print_num, 31337);    f_display_31337();     // 存储到成员函数的调用    std::function f_add_display = &foo::print_add;    const foo foo(314159);    f_add_display(foo, 1);    f_add_display(314159, 1);     // 存储到数据成员访问器的调用    std::function f_num = &foo::num_;    std::cout << num_:  << f_num(foo) << '';     // 存储到成员函数及对象的调用    using std::_1;    std::function f_add_display2 = std::bind( &foo::print_add, foo, _1 );    f_add_display2(2);     // 存储到成员函数和对象指针的调用    std::function f_add_display3 = std::bind( &foo::print_add, &foo, _1 );    f_add_display3(3);     // 存储到函数对象的调用    std::function f_display_obj = printnum();    f_display_obj(18);     auto factorial = [](int n) {        // 存储 lambda 对象以模拟递归 lambda ,注意额外开销        std::function fac = [&](int n){ return (n < 2) ? 1 : n*fac(n-1); };        // note that auto fac = [&](int n){...}; does not work in recursive calls        return fac(n);    };    for (int i{5}; i != 8; ++i) { std::cout << i << ! =  << factorial(i) << ;  ; }}  
可能的输出
-94231337314160314160num_: 314159314161314162185! = 120;  6! = 720;  7! = 5040;  
回调函数
std::function 的应用之一: 结合 typedef 定义函数类型构造回调函数.
typedef std::function callback;class messageprocessor {private:    callback callback_;public:    messageprocessor(callback callback):callback_(callback){}    void processmessage(const std::string& msg) {        callback_(msg);    }};  


启扬智能基于QY-IMX6主板的智能电子公交站牌解决方案
家用和工业超声波清洗机有什么区别?
无线充电器NPO贴片电容完全可以使用CBB电容替代
从币安平台币BNB窥探区块链世界价值捕获
什么是白加黑技术 免杀技术之白加黑攻击防御技术分析
C++可调用Callable类型的总结
为什么要使用有源光缆(AOC)?
人脸识别考勤系统在学校应用,是如何提升校园安全的?
双重显示的音频指示器电路
模拟雷击浪涌脉冲生成电路的工作原理
智能家居未来的重要一步:开发跟踪占用者并自我适应其偏好的自动化系统
TVS保护原理和参数解读
加贺富仪艾电子的低功耗蓝牙传感评估解决方案
紫光国微上半年净利润13.92亿元,同比增长16.22%
用于可变扭矩和恒定扭矩的变速应用中的电机控制PWM模块
区块链对于教育领域有什么可以应用的
大陆未来将押注自动驾驶 虽然预计经济环境不会好转
TCL贬损海信激光电视,赔偿经济损失50万元
解析模拟示波器正确的使用方法
TruPwr均方根检波器ADL5511特性/数据资料