c++函数对象与lambda表达式实现原理

函数对象

函数对象: 拥有()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;

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值