函数对象
函数对象: 拥有()operator重载函数的对象即函数对象,函数对象类似C语言里面的函数指针,但在C++里为函数对象。
什么是函数对象
例如:我们C语言进行函数调用与C++中两个函数调用。
看起来它们好像一模一样,但是C语言中的sum(),是函数名为一个地址;但C++中的sum()为一个对象,sum调用自己的()重载函数将10与20传给sum调用的()运算符重载函数了,它接受两个实参,再执行a+b。 即:sum.operator()(10,20)
使用函数对象好处
函数指针实现compare可以比较大于或比较小于
#include <iostream>
using namespace std;
//使用C的函数指针解决
template<typename T>
bool mygreater(T a, T b)
{
return a > b;
}
template<typename T>
bool myless(T a, T b)
{
return a < b;
}
//compare是C++的库函数模板
template<typename T, typename Compare>
bool compare(T a, T b, Compare comp)
{
return comp(a, b);
}
int main()
{
cout << compare(10, 20, mygreater<int>) << endl;
cout << compare(10, 20, myless<int>) << endl;
return 0;
}
通过函数指针虽然解决了问题,但函数指针无法进行内联,有函数的调用开销,效率很低;即使将其能够写为内联函数,编译的时候还是不知道调用时mygreater,myless。这里是通过函数指针间接调用的,编译器在编译过程中看到函数指针时候,不知道其调用的哪个函数,只有运行时候才知道。因此,C++有一个专门的函数对象来解决这个问题。
使用C++中函数对象解决:
好处:
1.通过函数对象调用()运算符重载,可以省略函数的调用开销,比通过函数指针调用函数(不能内联调用)效率高。
2.因为函数对象是用类生成的,所以可以添加相关的成员变量用来记录函数对象使用时更多的信息
#include <iostream>
using namespace std;
//C++函数对象实现
template<typename T>
class mygreater
{
public:
bool operator()(T a, T b)//二元函数对象
{
return a > b;
}
};
template<typename T>
class myless
{
public:
bool operator()(T a, T b)//二元函数对象
{
return a < b;
}
};
//compare是C++的库函数模板
template<typename T, typename Compare>
bool compare(T a, T b, Compare comp)
{
return comp(a, b);//编译过程知道调用对象的函数operator()(a, b);
}
int main()
{
cout << compare(10, 20, mygreater<int>()) << endl;
cout << compare(10, 20, myless<int>()) << endl;
return 0;
}
lambad表达式
lambda表达式是一种匿名表达式,一格式般定义为
捕捉列表:处于lambda函数开始位置,描述了上下文中哪些数据可以被lambda使用,以及使用的方式是传值还是传引用
- 参数列表:与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
- mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略
- ->返回值类型:与普通函数返回值一样表达函数的返回值类型,可以省略,编译器会对返回值类型进行推导。
- 函数体:可以使用参数以及捕获到的变量。
#include <iostream>
int main() {
//最简单的lamber表达式,没有任何意义
[] {};
//省略参数列表和返回值类型,由编译器推导
int a = 3, b = 4;
[=] {return a + b; };//函数体内的分号不要忘记
//引用传递的方式可以改变变量的值
auto func1 = [&](int c) {return b = a + c; };
func1(10);
//值传递捕获不能改变变量的值,除非使用mutable
auto fun2 = [=](int c)mutable {return b = a + c; };
fun2(10);
return 0;
}
lambda表达式底层实现原理:函数对象
#include <iostream>
using namespace std;
//无参 T= void
template<typename T=void>
class TestLambda01 {
public:
TestLambda01() {}
void operator()()const {
cout << "hello world";
}
};
//
template<typename T=int>
class TestLambda02 {
public:
TestLambda02() {}
int operator() (int a,int b) const {
return a + b;
}
};
template<typename T = int>
class TestLambda03
{
public:
TestLambda03(int &a, int &b) :ma(a), mb(b) {}
void operator()()const //修改的是ma ,mb指向空间的值,ma和mb本身没有修改 const 没有问题
{
int tmp = ma;
ma = mb;
mb = tmp;
}
private:
int &ma;
int &mb;
};
template<typename T = int>
class TestLambda04
{
public:
TestLambda04(int a, int b) :ma(a), mb(b) {}
void operator()()const
{
int tmp = ma;
ma = mb;
mb = tmp;
}
private:
mutable int ma;
mutable int mb;
};
int main()
{
//1 无参无返回值的lambda:
auto func1 = []() {cout << "hello world"; };
func1();
//等价于
TestLambda01<> t1;
t1();
//2 参数为整型返回值为整型的:
auto func2 = [](int a,int b ) {return a+b; };
func2(1,2);
//等价于
TestLambda02<> t2;
t2(1,2);
//3 以引用方式获取参数的lambda:
int a = 10;
int b = 20;
auto func3 = [&]() //
{
int tmp = a;
a = b;
b = tmp;
};
func3();
//等价于
TestLambda03<> t3(a,b);
t3();
//4 以值方式获取参数的lambda:
/*
auto func4 = [=]() //
{
int tmp = a;
a = b;
b = tmp;
};
报错: “a”: 无法在非可变 lambda 中修改通过复制捕获
原因:
class TestLambda04
{
public:
TestLambda04(int a, int b) :ma(a), mb(b) {}
void operator()()const
{
int tmp = ma;
ma = mb;
mb = tmp;
}
private:
int ma;
int mb;
};
operator()() const 不能修改成员变量ma mb值。如果想编译通过,需要加mutable
*/
auto func4 = [=]() mutable//并没有改变外面的a b ,传值
{
int tmp = a;
a = b;
b = tmp;
};
func4();
//等价于
TestLambda04<> t4(a, b);
t4();
std::cout << a << b;
return 0;
}
反汇编:
以func2为例:func2 反汇编后调用的是:lambda_aec96ee5e877c2989cfadc5720a77e88>::operator()
lambda表达式在编译期间被编译器自动转换成函数对象执行,调研operator
lambda的陷阱
按值捕获 & 捕获时机
#include <iostream>
#include <string>
using namespace std;
int main() {
std::string str = "abc";
auto localStr = [=](std::string &s) { s = str; };
str = "hello world";
std::string str1;
localStr(str1);
std::cout << str;
return 0;
}
str=abc;
当闭包生成的那一刻,被捕获的变量已经按值赋值的方式进行了捕获,后面那个str
再怎么变化,已经和闭包对象里面的值没有关系了.
localStr 中str中的值在localStr 定义的时候就已经确定为abc了,不会再发生变化。
按引用捕获 & 悬空引用
在C++编程中,程序员有责任保证Lambda调用的时候,保证被捕获的变量仍然有效~!是的,责任在你,而不在编译器。如果不能很好理解这点,就会遇到悬空引用的问题!
悬空引用( dangling references )就是说我们创建了一个对象的引用类型的变量,但是被引用的对象被析构了、无效了。一般情况下,引用类型的变量必须在初始化的时候赋值,很少遇到这种情况,但是如果lambda被延迟调用,在调用时,已经脱离了当前的作用域,那么按引用捕获的对象就是悬空引用。
#include <iostream>
#include <string>
#include <functional>
using namespace std;
std::function<void(void)> func;
void test() {
std::string str = "abc";
func = [&]() { std::string a = str; cout << a; };
}
int main() {
test();
func();
return 0;
}
发生crash
原因:func引用的对象str在离开作用域test()后被析构了,在main函数中执行func()导致找到引用的对象str,导致crash;