C++_3——库(functional)与函数对象
??这一系列文章的目的是在学习了C++基础后,继续补充一些C++基础和进阶的知识点,包括C++11的相关内容。 以C++11标准为基础。
C++网站:http://www.cplusplus.com/reference/
1 函数对象
任何定义了函数调用操作符的对象都是函数对象(也叫仿函数),C++ 支持创建、操作新的函数对象,同时也提供了许多内置的函数对象,可以像调用函数一样使用。可以自定义函数类如下:
class MyType{
public:
void operator()(){
std::cout<<"mmmm\n";
}
void operator()(int bbb){
std::cout<<bbb+10<<"\n";
}
int operator()(char a){
if(isdigit(a))
return atoi(&a);
else
return -1;
}
};
#include "func.h"
int main(){
MyType val;
val();
val(123);
std::cout<<val('a')<<std::endl;
}
2 lambda表达式
利用lambda表达式可以编写内嵌的匿名函数,替换独立函数或者函数对象,不用写一个额外的函数或类或结构体,并且使代码更可读。
-
基本使用 [ 捕获 ] ( 形参 ) -> 返回类型 { 函数体 } -
捕获指明内部可以访问的外部变量,可以是值捕获,也可以是引用捕获 -
返回类型可以省略,由lambda表达式自动推导结果 -
编译器会将lambda表达式生成为一个定义了函数调用操作符的类/结构体,这个类/结构体是一个闭包类型,因为捕获了封装作用域内的变量。 -
举几个例子 #include <iostream>
#include <vector>
#include <string>
#include <algorithm>
int main(){
auto print = [](int s) {std::cout << "value is " << s << std::endl;};
auto lambAdd = [](int aa, int bb) ->int { return aa + bb;};
int iSum = lambAdd(10, 11);
print(iSum);
int count = 0;
std::vector<std::string> words{ "An", "ancient", "Pond" };
std::for_each(words.cbegin(), words.cend(),
[&count](const std::string& word)
{
if(isupper(word[0])) {
std::cout << word << " " << count << std::endl;
count++;
}
});
}
-
能不能修改捕获的变量? 引用捕获,可以修改。 值捕获时,不允许修改变量,这与函数的值传递不一样。若想修改,需要关键字mutabe,修改上面的例子如下。要注意所有修改在lambda表达式之外无效,这就跟函数传递值一样了。 #include <iostream>
#include <vector>
#include <string>
#include <algorithm>
int main(){
int count = 0;
std::vector<std::string> words{ "An", "ancient", "Pond" };
std::for_each(words.cbegin(), words.cend(),
[count](const std::string& word) mutable
{
if(isupper(word[0])) {
std::cout << word << " " << count << std::endl;
count++;
}
});
std::cout<<count<<"\n";
}
-
捕获还是直接传参? 考虑传参是否会生成临时变量存储传参值 捕获不会生成临时变量,例外情况如 值捕获使用mutable -
lambda表达式不能=赋值,可以()初始化拷贝
3 functional
-
内置函数对象 #include <algorithm>
#include <functional>
int first[]={1,2,3,4,5};
int second[]={10,20,30,40,50};
int results[5];
std::transform (first, first+5, second, results, std::plus<int>());
int numbers[]={10,10,10,20,20};
int* pt = std::adjacent_find (numbers, numbers+5, std::not_equal_to<int>()) + 1;
-
std::function
Class that can wrap any kind of callable element (such as functions and function objects) into a copyable object, and whose type depends solely on its call signature (and not on the callable element type itself).
std::function 对象可以包装多种可调用实体,如普通函数、函数指针、函数对象、lambda表达式、指向成员函数的指针、指向成员变量的指针等,std::function 对象与可调用实体的类型无关,与可调用实体的签名(输入和返回类型)有关,官网举例如下。
#include <iostream>
#include <functional>
int half(int x) {return x/2;}
struct third_t {
int operator()(int x) {return x/3;}
};
struct MyValue {
int value;
int fifth() {return value/5;}
};
int main () {
std::function<int(int)> fn1 = half;
std::function<int(int)> fn2 = ½
std::function<int(int)> fn3 = third_t();
std::function<int(int)> fn4 = [](int x){return x/4;};
std::function<int(int)> fn5 = std::negate<int>();
std::cout << "fn1(60): " << fn1(60) << '\n';
std::cout << "fn2(60): " << fn2(60) << '\n';
std::cout << "fn3(60): " << fn3(60) << '\n';
std::cout << "fn4(60): " << fn4(60) << '\n';
std::cout << "fn5(60): " << fn5(60) << '\n';
std::function<int(MyValue&)> value = &MyValue::value;
std::function<int(MyValue&)> fifth = &MyValue::fifth;
MyValue sixty {60};
std::cout << "value(sixty): " << value(sixty) << '\n';
std::cout << "fifth(sixty): " << fifth(sixty) << '\n';
return 0;
}
std::function 对象可以swap。std::function 对象可以用nullptr 判断是否为空,为空时调用将抛出std::bad_function_call 异常。 #include <iostream>
#include <functional>
int main () {
std::function<int(int,int)> foo,bar;
foo = std::plus<int>();
foo.swap(bar);
std::cout << "foo is " << (foo ? "callable" : "not callable") << ".\n";
std::cout << "bar is " << (bar!=nullptr ? "callable" : "not callable") << ".\n";
return 0;
}
- 为什么使用
std::function ?统一封装,简化调用,类型擦除… std::function 对象内部是否存有可调用实体的地址?不同类型的可调用实体如何判断?各种内部原理?这篇博客…可以看看 -
bind & placeholder
- bind绑定一个可调用实体(函数对象、函数指针、成员指针),返回基于该实体的函数对象。
- 可调用实体的每个输入参数可以绑定一个值或占位符(placeholder)。绑定值时,返回函数对象调用时,将使用该值。绑定占位符时,返回函数对象调用时,将根据占位符顺序依次传递。
- 可以通过模板指定返回类型。
- 绑定成员指针时,绑定/传入的第一个参数要求是成员所在类的一个实例化对象,如果是成员函数还有其他参数,那么就继续绑定第二个、第三个…参数。
- 简单用法:
#include <iostream>
#include <functional>
double my_divide (double x, double y) {return x/y;}
struct MyPair {
double a,b;
double multiply() {return a*b;}
};
int main () {
using namespace std::placeholders;
auto fn_five = std::bind (my_divide,10,2);
std::cout << fn_five() << '\n';
auto fn_half = std::bind (my_divide,_1,2);
std::cout << fn_half(10) << '\n';
auto fn_invert = std::bind (my_divide,_2,_1);
std::cout << fn_invert(10,2) << '\n';
auto fn_rounding = std::bind<int> (my_divide,_1,_2);
std::cout << fn_rounding(10,3) << '\n';
MyPair ten_two {10,2};
auto bound_member_fn = std::bind (&MyPair::multiply,_1);
std::cout << bound_member_fn(ten_two) << '\n';
auto bound_member_data = std::bind (&MyPair::a,ten_two);
std::cout << bound_member_data() << '\n';
return 0;
}
- placeholder为占位符,如
std::placeholders::_1 、std::placeholders::_2 、std::placeholders::_3 等等,理论上数字可以一直增加,需要注意的是这些是变量,或者叫对象,是object。判断一个变量是不是占位符可以用std::is_placeholder<decltype(std::placeholders::_1)>::value
4 一些用于理解的引用
既然用函数对象与调用普通函数有相同的效果,为什么还有搞这么麻烦定义一个类来使用函数对象?主要在于函数对象有以下的优势:
- 函数对象可以有自己的状态。我们可以在类中定义状态变量,这样一个函数对象在多次的调用中可以共享这个状态。但是函数调用没这种优势,除非它使用全局变量来保存状态。
- 函数对象有自己特有的类型,而普通函数无类型可言。这种特性对于使用C++标准库来说是至关重要的。这样我们在使用STL中的函数时,可以传递相应的类型作为参数来实例化相应的模板,从而实现我们自己定义的规则。
来自 C++中的函数对象 - 端木月岛的文章 - 知乎
传递函数指针参数时,使用函数对象替代一些函数指针有利于代码复用和扩展 来自 C++ 仿函数
附录
lambda表达式捕获类型
| | | |
---|
[] | 默认不捕获任何变量 | | | [=] | 默认以复制捕获所有变量,少用 | [&] | 默认以引用捕获所有变量,少用 | [x] | 仅以复制捕获x,其它变量不捕获 | [x…] | 以包展开方式复制捕获参数包变量,也就是可变参数 | [&x] | 仅以引用捕获x,其它变量不捕获 | [&x…] | 以包展开方式引用捕获参数包变量,也就是可变参数 | [=, &x] | 默认以复制捕获所有变量,但是x是例外,通过引用捕获 | [&, x] | 默认以引用捕获所有变量,但是x是例外,通过复制捕获 | [this] | 通过引用捕获当前对象(其实是复制指针) | [*this] | 通过复制方式捕获当前对象 |
更多内置函数对象类型
| 内置函数对象类 | 操作符重载 | 类似其他内置函数对象类 |
---|
四则 | std::plus | T operator() (const T& x, const T& y) const {return x+y;} | std::minus std::multiplies
std::divides | 大小 | std::not_equal_to | bool operator() (const T& x, const T& y) const {return x!=y;} | std::less_equal std::less
std::greater_equal std::greater std::equal_to | 取反 | std::negate | T operator() (const T& x) const {return -x;} | | 取余 | std::modulus | T operator() (const T& x, const T& y) const {return x%y;} | | 逻辑 | std::logical_or | bool operator() (const T& x, const T& y) const {return x||y;} | std::logical_not
std::logical_and | 位运算 | std::bit_and | T operator() (const T& x, const T& y) const {return x&y;} | std::bit_xor
std::bit_or |
参考
- C++中的函数对象
- https://www.apiref.com/cpp-zh/cpp/utility/functional.html
- C++ 仿函数
- c++中lambda表达式用法
- 剖析STD::FUNCTION接口与实现
- C++类型擦除与
std::function 性能探索
|