当前位置:   article > 正文

C++可调用Callable类型的总结_callable撖寡情c++

callable撖寡情c++

C++进阶专栏:http://t.csdnimg.cn/afM80

相关系列文章

C++17之std::invoke: 使用和原理探究(全)

目录

1.简介

2.Callable 类型

3.可作为参数的标准库

4.一些典型的 Callable 类型

4.1.函数对象 Function Object

4.2.函数

4.3.函数指针

4.4.Lambda 匿名函数(调用对象)

4.5.函数适配器

4.6.std::function


1.简介

        自从在使用 std::thread 构造函数过程中遇到了 Callable 类型的概念以来用到了很多关于它的使用。因此本文把使用/调查结果总结出来,包括 Callable 的基础概念,典型的 Callable 类型介绍。例如仿函数函数指针, lambda 匿名函数,函数适配器, std::function 等。

2.Callable 类型

定义(参考):可调用(Callable) 类型是可应用 INVOKE 操作(std::invoke 是在 C++17 里定义的类, 感觉意思就是执行函数操作的模板类.)

要求:一个 T 类型要满足为 callable 需要以下表达式在不求值语境中良构.INVOKE<R>(f, [std::declval]ArgTypes>()...) 即 INVOKE<R>(f, t1, t2, ..., tN).其中 f 为 T 类型的对象, ArgTypes 为适合的实参类型列表, R 为适合的返回类型.R为 void 的时可以表示为 static_cast<void>(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。

3.可作为参数的标准库

下列标准库设施接受任何可调用(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)打包一个函数, 存储其返回值以进行异步获取 (类模板)

4.一些典型的 Callable 类型

4.1.仿函数 Function Object

一个重载了括号操作符()的对象, 也就是可以以f(args)形式进行函数调用的对象。

  1. #include<iostream>
  2. #include<cstdlib>
  3. using namespace std;
  4. class Add {
  5. public:
  6. const int operator()(const int a,const int b){
  7. return a+b;}
  8. };
  9. int main() {
  10. Add addFunction; //函数对象
  11. cout<<addFunction(2, 3)<<endl;// 5
  12. system("pause");
  13. return 0;
  14. }

        我的第一印象是它跟函数指针有什么区别? 就像是个函数执行包装器, 一个对象型的函数指针?

        但是函数对象本质上还是一个 class 的具体化 object, 里面是可以附带一些成员变量(可以理解为函数对象的状态(state))的, 这就让函数对象的应用场景比函数指针更广阔. 最典型的便是 STL 里了. C++ 的 STL 中的众多 algorithm, 非常依赖于函数对象处理容器的元素. 想按照 STL 算法里的要求实现其功能要提供一些函数对象作为参数, 即谓词参数(predicate). 例如对于 find_if 算法.

  1. class NoLess{
  2. public:
  3. NoLess(int min = 0):m_min(min){}
  4. bool operator() (int value) const{
  5. return value >= m_min;}
  6. private:
  7. int m_min;
  8. };
  9. find_if(dest.begin(),dest.end(),NoLess(10)); //dest容器里找是否存在不小于10的元素

        对于普通函数来说, 只要签名一致, 其类型就是相同的, 是类型不安全的. 但是这并不适用于函数对象, 因为函数对象的类型是其类的类型. 这样, 函数对象有自己的类型, 这也意味着函数对象可以用于模板参数, 这对泛型编程有很大提升. 因为函数对象一般用于模板参数, 模板一般会在编译时会做一些优化. 因此函数对象一般快于普通函数. 类也可以在使用的时候动态再产生, 节省成本.

        既然是类, 那就有它的限制, 例如要注意, 如同其他所有对象(狭义上的对象, 我感觉内置类型其实也可以被叫对象, 按场景区分吧)一样, 如果 pass-by-value 的化, 对象里的成员变量是被复制进去的, 一旦对象被析构了, 里面的成员变量也是无法保存下来的. 所以可以 pass-by-reference/pointer.

        函数指针并不是没有其用处了, 对于 C API 库里的某些函数不支持函数对象还是有用武之地的. 例如 <cstdlib> 里面的排序函数 qsort 只能调用函数指针.

void qsort( void *ptr, size_t count, size_t size,int (*comp)(const void *, const void *) );

4.2.函数

除了普通的函数, 当然也包括类成员函数。
这里不提及模板函数,因为模板函数的概念只存在于编译期, 运行期的函数没有模板的概念, 都是经过完全特化过的, 因此与普通函数/类成员函数的概念是一致的。

4.3.函数指针

  1. #include<iostream>
  2. #include<cstdlib>
  3. using namespace std;
  4. int AddFunc(int a, int b) {
  5. return a + b;}
  6. int main() {
  7. int (*Add1) (int a, int b); //函数指针,函数名两侧的()不可省略
  8. int (*Add2) (int a, int b);
  9. Add1 = &AddFunc;
  10. Add2 = AddFunc;
  11. cout << (*Add1) (3, 2)<<endl;
  12. cout<<Add1(3, 2)<<endl; //输出可以加*,也可以不加
  13. system("pause");
  14. return 0;
  15. }

4.4.Lambda 匿名函数(调用对象)

好处是就地定义使用, 简洁, 易维护.

完整声明:

[capture list] (params list) mutable exception-> return type { function body }

各项具体含义如下

  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. 延迟调用
按值捕获与按引用捕获的区别.

  1. int a = 0;
  2. auto f = [=]{ return a; }; // 按值捕获外部变量
  3. a += 1; // a被修改了
  4. std::cout << f() << std::endl; // 输出依旧为0,如果想要跟着被改变需要使用引用捕获

2. lambda 表达式转换成函数指针没有捕获变量的 lambda 表达式可以直接转换为函数指针, 而捕获变量的 lambda 表达式则不能转换为函数指针.

  1. typedef void(*Ptr)(int*);
  2. Ptr p = [](int* p){delete p;}; // 正确, 没有状态的 lambda (没有捕获)的lambda表达式可以直接转换为函数指针
  3. Ptr p1 = [&](int* p){delete p;}; // 错误, 有状态的 lambda 不能直接转换为函数指针

3. 嵌套

int m = [](int x) { return [](int y) { return y * 2; }(x)+6; }(5); //16

4. 作为 STL 算法函数谓词参数:

  1. std::vector<int> myvec{ 3, 2, 5, 7, 3, 2 };
  2. std::sort(myvec.begin(), myvec.end(), [](int a, int b) -> bool { return a < b; });

C++14 中的 lambda 新特性

1. lambda 捕捉表达式/右值

  1. // 利用表达式捕获,可以更灵活地处理作用域内的变量
  2. int x = 4;
  3. auto y = [&r = x, x = x + 1] { r += 2; return x * x; }();
  4. // 此时 x 更新为6,y 为25
  5. // 直接用字面值初始化变量
  6. auto z = [str = "string"]{ return str; }();
  7. // 此时z是const char* 类型,存储字符串 string
  8. //不能复制只能移动的对象,可以用std::move初始化变量
  9. auto myPi = std::make_unique<double>(3.1415);
  10. auto circle_area = [pi = std::move(myPi)](double r) { return *pi * r * r; };
  11. cout << circle_area(1.0) << endl; // 3.1415

2. 泛型 lambda 表达式:

  1. auto add = [](auto x, auto y) { return x + y; };//推断类型
  2. int x = add(2, 3); // 5
  3. double y = add(2.5, 3.5); // 6.0

4.5.函数适配器

将函数对象与其它函数对象, 或者特定的值, 或者特定的函数相互组合的产物. 由于组合特性, 函数适配器可以满足特定的需求, 头文件 <functional> 定义了几种函数适配器:

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 封装出很多有意思的功能.

1) 嵌套

  1. auto plus10times2 = std::bind(std::multiplies<int>{},
  2. std::bind(std::plus<int>{}, std::placeholders::_1, 10), 2);
  3. cout << plus10times2(4) << endl; // 输出: 28
  4. // 定义3次方函数对象
  5. auto pow3 = std::bind(std::multiplies<int>{},
  6. std::bind(std::multiplies<int>{}, std::placeholders::_1, std::placeholders::_1),
  7. std::placeholders::_1);
  8. cout << pow3(3) << endl; // 输出: 27

2) 调用类中的成员函数

  1. class Person{
  2. public:
  3. Person(const string& n) : name{ n } {}
  4. void print() const { cout << name << endl; }
  5. void print2(const string& prefix) { cout << prefix << name << endl; }
  6. private:
  7. string name;
  8. };
  9. int main()
  10. {
  11. vector<Person> p{ Person{"Tick"}, Person{"Trick"} };
  12. // 调用成员函数print
  13. std::for_each(p.begin(), p.end(), std::bind(&Person::print, std::placeholders::_1));
  14. // 此处的std::placeholders::_1表示要调用的Person对象,所以相当于调用arg1.print()
  15. // 输出: Tick Trick
  16. std::for_each(p.begin(), p.end(), std::bind(&Person::print2, std::placeholders::_1,
  17. "Person: "));
  18. // 此处的std::placeholders::_1表示要调用的Person对象,所以相当于调用arg1.print2("Person: ")
  19. // 输出: Person: Tick Person: Trick
  20. return 0;
  21. }

3) 调用 lambda 表达式

  1. vector<int> data{ 1, 2, 3, 4 };
  2. auto func = std::bind([](const vector<int>& data) { cout << data.size() << endl; },
  3. std::move(data));
  4. func(); // 4
  5. cout << data.size() << endl; // 0

4)  调用范围内函数

  1. char myToupper(char c){
  2. if (c >= 'a' && c <= 'z')
  3. return static_cast<char>(c - 'a' + 'A');
  4. return c;
  5. }
  6. int main()
  7. {
  8. string s{ "Internationalization" };
  9. string sub{ "Nation" };
  10. auto pos = std::search(s.begin(), s.end(), sub.begin(), sub.end(),
  11. std::bind(std::equal_to<char>{},
  12. std::bind(myToupper, std::placeholders::_1),
  13. std::bind(myToupper, std::placeholders::_2)));
  14. if (pos != s.end()){
  15. cout << sub << " is part of " << s << endl;
  16. }
  17. // 输出: Nation is part of Internationalization
  18. return 0;
  19. }

5) 默认 pass-by-value, 如果想要 pass-by-reference, 需要用 std::ref 和 std::cref 包装.
std::cref 比 std::ref 增加 const 属性.

  1. void f(int& n1, int& n2, const int& n3){
  2. cout << "In function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
  3. ++n1;
  4. ++n2;
  5. // ++n3; //无法编译
  6. }
  7. int main()
  8. {
  9. int n1 = 1, n2 = 2, n3 = 3;
  10. auto boundf = std::bind(f, n1, std::ref(n2), std::cref(n3));
  11. n1 = 10;
  12. n2 = 11;
  13. n3 = 12;
  14. cout << "Before function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
  15. boundf();
  16. cout << "After function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
  17. // Before function : 10 11 12
  18. // In function : 1 11 12
  19. // After function : 10 12 12
  20. return 0;
  21. }

std::mem_fn

        与 std::bind 相比, std::mem_fn 的范围又要小一些, 仅调用成员函数, 并且可以省略掉用于调用对象的占位符.
        因此使用 std::men_fn 不需要绑定参数, 可以更方便地调用成员函数.

  1. vector<Person> p{ Person{ "Tick" }, Person{ "Trick" } };
  2. std::for_each(p.begin(), p.end(), std::mem_fn(&Person::print));
  3. // 输出: Trick Trick
  4. Person n{ "Bob" };
  5. std::mem_fn(&Person::print2)(n, "Person: ");
  6. // 输出: Person: Bob

std::mem_fn 还可以调用成员变量

  1. class Foo{
  2. public:
  3. int data = 7;
  4. void display_greeting() { cout << "Hello, world.\n"; }
  5. void display_number(int i) { cout << "number: " << i << '\n'; }
  6. };
  7. int main()
  8. {
  9. Foo f;
  10. // 调用成员函数
  11. std::mem_fn(&Foo::display_greeting)(f); // Hello, world.
  12. std::mem_fn(&Foo::display_number)(f, 20); // number: 20
  13. // 调用数据成员
  14. cout << std::mem_fn(&Foo::data)(f) << endl; // 7
  15. return 0;
  16. }

std::not1 、std::not2、std::unary_negate、std::binary_negate

std::not1std::not2 分别构造一个与谓词结果相反的一元/二元函数对象.
std::unary_negatestd::binary_negate 分别返回其所保有的一元/二元谓词的逻辑补的包装函数对象, 其对象一般为 std::not1std::not2 构造的函数对象,即又加了一层包装.
下面分别是其使用示例:

  1. //std::not1
  2. #include <iostream>
  3. #include <vector>
  4. #include <functional>
  5. int main(int argc, char **argv)
  6. {
  7. std::vector<int> nums = {5, 3, 4, 9, 1, 7, 6, 2, 8};
  8. std::function<bool(int)> less_than_5 = [](int x){ return x <= 5; };
  9. // count numbers of integer that not less and equal than 5
  10. std::cout << std::count_if(nums.begin(), nums.end(), std::not1(less_than_5)) << "\n";
  11. //输出结果4
  12. return 0;
  13. }
  14. //std::not2
  15. using namespace std;
  16. int main(int argc, char **argv)
  17. {
  18. std::vector<int> nums = {5, 3, 4, 9, 1, 7, 6, 2, 8};
  19. std::function<bool(int, int)> ascendingOrder = [](int a, int b) { return a<b; };
  20. // sort the nums in descending order: not ascending order
  21. std::sort(nums.begin(), nums.end(), std::not2(ascendingOrder));
  22. for(int i:nums) {
  23. std::cout << i << "\t";}
  24. //输出结果: 9 8 7 6 5 4 3 2 1
  25. return 0;
  26. }
  27. //std::unary_negate
  28. #include <algorithm>
  29. #include <functional>
  30. #include <iostream>
  31. #include <vector>
  32. struct less_than_7 : std::unary_function<int, bool>{
  33. bool operator()(int i) const { return i < 7; }
  34. };
  35. int main()
  36. {
  37. std::vector<int> v;
  38. for (int i = 0; i < 10; ++i) v.push_back(i);
  39. std::unary_negate<less_than_7> not_less_than_7((less_than_7()));
  40. std::cout << std::count_if(v.begin(), v.end(), not_less_than_7);
  41. //输出8 9
  42. }
  43. //std::binary_negate
  44. struct same : std::binary_function<int, int, bool>{
  45. bool operator()(int a, int b) const { return a == b; }
  46. };
  47. int main()
  48. {
  49. std::vector<int> v1;
  50. std::vector<int> v2;
  51. for (int i = 0; i < 10; ++i) v1.push_back(i);
  52. for (int i = 0; i < 10; ++i) v2.push_back(10 - i);
  53. std::vector<bool> v3(v1.size());
  54. std::binary_negate<same> not_same((same()));
  55. std::transform(v1.begin(), v1.end(), v2.begin(), v3.begin(), not_same);
  56. std::cout.setf(std::ios_base::boolalpha);
  57. for (int i = 0; i < 10; ++i)
  58. std::cout << v1[i] << ' ' << v2[i] << ' ' << v3[i] << ' ';
  59. //输出:0 10 true 1 9 true 2 8 true 3 7 true 4 6 true 5 5 false 6 4 true 7 3 true 8 2 true 9 1 true
  60. }

std::not_fn

注意 C++17 已经把上面的 std::not1std::not2std::unary_negate 和 std::binary_negate 抛弃, 统一由 std::not_fn 替代.

  1. //移除把满足谓词p的元素都copy到容器中
  2. template <typename T, typename Pred>
  3. auto FilterRemoveCopyIf(const std::vector<T>& vec, Pred p) {
  4. std::vector<T> out;
  5. std::remove_copy_if(begin(vec), end(vec),
  6. std::back_inserter(out), std::not_fn(p));
  7. return out;
  8. }

4.6.std::function

        五花八门的 Callable, 个个都是人才, 但是不好带(不好实现 generic programming), 所以一个把所有 callable 对象封装成统一形式的类型模板.
   std::function 的实例可以对任何可以调用的目标实体进行存储, 复制, 和调用操作, 实现一种类型安全的包裹.

        原型为:

  1. template< class R, class... Args > //R是返回值类型,Args是函数的参数类型
  2. class function<R(Args...)>;

        其存储的可调用对象被称为 std::function 的目标. 若 std::function 不含目标, 则称它为空. 调用空 std::function 的目标导致抛出 std::bad_function_call 异常.
   std::function 满足可复制构造 (Copy Constructible) 和可复制赋值 (Copy Assignable) (参考).瑞士军刀一般的功能, 代码例子如下:

  1. #include <functional>
  2. #include <iostream>
  3. struct Foo {
  4. Foo(int num) : num_(num) {}
  5. void print_add(int i) const { std::cout << num_+i << '\n'; }
  6. int num_;
  7. };
  8. void print_num(int i){
  9. std::cout << i << '\n';
  10. }
  11. struct PrintNum {
  12. void operator()(int i) const
  13. {
  14. std::cout << i << '\n';
  15. }
  16. };
  17. int main()
  18. {
  19. // 存储自由函数
  20. std::function<void(int)> f_display = print_num;
  21. f_display(-9);
  22. // 存储 lambda
  23. std::function<void()> f_display_42 = []() { print_num(42); };
  24. f_display_42();
  25. // 存储到 std::bind 调用的结果
  26. std::function<void()> f_display_31337 = std::bind(print_num, 31337);
  27. f_display_31337();
  28. // 存储到成员函数的调用
  29. std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
  30. const Foo foo(314159);
  31. f_add_display(foo, 1);
  32. f_add_display(314159, 1);
  33. // 存储到数据成员访问器的调用
  34. std::function<int(Foo const&)> f_num = &Foo::num_;
  35. std::cout << "num_: " << f_num(foo) << '\n';
  36. // 存储到成员函数及对象的调用
  37. using std::placeholders::_1;
  38. std::function<void(int)> f_add_display2 = std::bind( &Foo::print_add, foo, _1 );
  39. f_add_display2(2);
  40. // 存储到成员函数和对象指针的调用
  41. std::function<void(int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 );
  42. f_add_display3(3);
  43. // 存储到函数对象的调用
  44. std::function<void(int)> f_display_obj = PrintNum();
  45. f_display_obj(18);
  46. auto factorial = [](int n) {
  47. // 存储 lambda 对象以模拟"递归 lambda ",注意额外开销
  48. std::function<int(int)> fac = [&](int n){ return (n < 2) ? 1 : n*fac(n-1); };
  49. // note that "auto fac = [&](int n){...};" does not work in recursive calls
  50. return fac(n);
  51. };
  52. for (int i{5}; i != 8; ++i) { std::cout << i << "! = " << factorial(i) << "; "; }
  53. }

可能的输出

  1. -9
  2. 42
  3. 31337
  4. 314160
  5. 314160
  6. num_: 314159
  7. 314161
  8. 314162
  9. 18
  10. 5! = 120; 6! = 720; 7! = 5040;

回调函数

std::function 的应用之一: 结合 typedef 定义函数类型构造回调函数.

  1. typedef std::function<void(std::string)> CallBack;
  2. Class MessageProcessor {
  3. private:
  4. CallBack callback_;
  5. public:
  6. MessageProcessor(Callback callback):callback_(callback){}
  7. void ProcessMessage(const std::string& msg) {
  8. callback_(msg);
  9. }
  10. };

参考:

C++ 具名要求:可调用 (Callable) - cppreference.com 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/555656
推荐阅读
相关标签
  

闽ICP备14008679号