admin 管理员组

文章数量: 886992

现代c++急急急——functional、bind、lambda

文章目录

  • 现代c++急急急——functional、bind、lambda
    • bind前世今生
      • 从一个排序的例子开始
      • 可能的实现
      • 今生
    • 函数对象
      • 从hello开始
      • 可能的实现
    • lambda
      • 例子
        • 从hello开始
        • 带函数参数的lambda
        • 捕获外界变量的lambda

现代c++急急急——functional、bind、lambda

bind前世今生

bind1st:operator()的第一个参数成为一个既定的值

bind2nd:operator()的第二个参数成为一个既定的值

从一个排序的例子开始

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>using namespace std;template<class T>
void MyPrint(T& container)
{for (auto x : container){cout << x << ' ';}cout << endl;
}int main()
{vector<int> vec;srand(time(NULL));for (int i = 0; i < 20; i++){vec.push_back(rand() % 100 + 1);}MyPrint(vec);sort(vec.begin(), vec.end());//sort(vec.begin(), vec.end(), greater<int>());MyPrint(vec);return 0;
}

以上代码生成一个vector并从小到大排序

sort第三个参数支持自定义的排序方法,这里我们传入的是greater,实现从大到小排序,greater实现两个元素之间的比较

那么现在需要把70插入容器中,要求容器顺序不变,该怎么做?

在STL中有一个find_if可以在遍历某个容器的时候,按某个条件查找,比如说本次要求,只需要挨个拿容器中的某个元素和70进行比较,返回bool即可

但是我们注意到,find_if拿一个元素和一个既定的元素做比较,而greater或者less的都是拿两个元素做比较,有没有办法实现greater其中的一个元素固定下来呢?

这就是绑定器的作用,比如说让greater的第一个元素固定为70

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>using namespace std;template<class T>
void MyPrint(T& container)
{for (auto x : container){cout << x << ' ';}cout << endl;
}int main()
{vector<int> vec;srand(time(NULL));int num = 0;cin >> num;for (int i = 0; i < 20; i++){vec.push_back(rand() % 100 + 1);}MyPrint(vec);sort(vec.begin(), vec.end(), greater<int>());auto res = find_if(vec.begin(), vec.end(), bind1st(greater<int>(), num));if (res != vec.end()){vec.insert(res, num);}MyPrint(vec);return 0;
}

同理,bind2nd也是一样的效果

可能的实现

find_if

// 取自cppreference
template<class InputIterator, class UnaryPredicate>InputIterator find_if (InputIterator first, InputIterator last, UnaryPredicate pred)
{while (first!=last) {if (pred(*first)) return first;++first;}return last;
}

bind1st

template<class Compare, class T>
class _MyBind1st
{
public:_MyBind1st(Compare cmp, T val) : _cmp(cmp), _val(val) {};bool operator()(const T& secondElement){return _cmp(_val, secondElement);}
private:Compare _cmp;T _val;
};
template<class Compare, class T>
_MyBind1st<Compare, T> myBind1st(Compare cmp, const T& val)
{return _MyBind1st<Compare, T>(cmp, val);
}

最终效果

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>using namespace std;template<class T>
void MyPrint(T& container)
{for (auto x : container){cout << x << ' ';}cout << endl;
}template<class Compare, class T>
class _MyBind1st
{
public:_MyBind1st(Compare cmp, T val) : _cmp(cmp), _val(val) {};bool operator()(const T& secondElement){return _cmp(_val, secondElement);}
private:Compare _cmp;T _val;
};
template<class Compare, class T>
_MyBind1st<Compare, T> myBind1st(Compare cmp, const T& val)
{return _MyBind1st<Compare, T>(cmp, val);
}int main()
{vector<int> vec;srand(time(NULL));int num = 0;cin >> num;for (int i = 0; i < 20; i++){vec.push_back(rand() % 100 + 1);}MyPrint(vec);sort(vec.begin(), vec.end(), greater<int>());auto res = find_if(vec.begin(), vec.end(), myBind1st(greater<int>(), num));if (res != vec.end()){vec.insert(res, num);}MyPrint(vec);return 0;
}

运行结果正确,由此可知,绑定器是函数对象的一种用法,使得某个函数的某个参数成为既定的参数(既定的意思并不是说右值)

今生

#include <iostream>
#include <functional>
#include <string>using namespace std;void Say(string str)
{cout << str << endl;
}int main()
{auto res = bind(Say, "hello");res();return 0;
}

与1st和2nd不同的是,bind可以使得任意一个参数绑定为指定的参数,而不是固定的位置(如1st只能第一个参数变成指定的参数)

当然,bind也有1st和2nd的功能,也就是20个占位符,用于既定的位置的参数指定成给定的参数(很绕,我知道,这句话看一遍就好,不要深陷其中,看下面的例子)

一个实际的例子:

#include <iostream>
#include <functional>
#include <string>using namespace std;void Say(string str)
{cout << str << endl;
}int main()
{// 指明Say函数的第一个参数(指定的位置)由用户指定auto res = bind(Say, placeholders::_1);res("hello");return 0;
}

以上效果便是第一个参数被指定为由用户给出的参数

函数对象

从hello开始

#include <iostream>
#include <functional>
#include <string>using namespace std;void SayStr(string str)
{cout << str << endl;
}void Say()
{cout << "hello" << endl;
}int main()
{function<void()> funcObj = Say;function<void(string)> funcObj2 = SayStr;funcObj();funcObj2("hello");return 0;
}

可以看到,函数对象有点类似于函数指针,但是背后原理其实是模板类,重载了()运算符,只是效果类似于函数指针,可以“指向”函数(注意有一个引号)

那么有趣的就是,函数这个概念,除了普通的函数,bind和lambda表达式都可以理解为返回了一个函数

除此之外,其实还有类内函数(比较重要的OOP手法)

#include <iostream>
#include <functional>
#include <string>using namespace std;class Say
{
public:void SayHello(string str) { cout << str << endl; };
};int main()
{function<void(Say*, string)> funcObj = &Say::SayHello;Say tmpObj;funcObj(&tmpObj, "hello world");return 0;
}

通过以上例子,可以看出要使得functional可以“指向”类内函数,需要满足以下的条件

  • 类内函数为Public
  • 实例化function对象的函数签名需要有一个类的指针(也就是this指针)
  • 需要一个具体的类的对象

那么有趣的是,bind的参数也是一个函数,因此也可以通过传入的参数“指向”某个类的成员函数,这么做的好处是什么?

有点类似友元函数 + 回调函数的概念了,可以在不同的地方回调这个函数,极大的增加了代码的复用

可能的实现

#include <iostream>
#include <string>
#include <functional>using namespace std;void Say(string str)
{cout << str << endl;
}template<class Fty>
class myFunction {};template<class R, class Arg>
class myFunction<R(Arg)>
{
public:using pFunc = R(*)(Arg);myFunction(pFunc func) : func_(func) {};R operator() (Arg arg){return func_(arg);};
private:pFunc func_;
};int main()
{myFunction<void(string)> func(Say);func("hello world");return 0;
}

这里可以看出其实就是使用了函数指针来实现的,唯一的差别就是,这个实现只是一个参数,官方实现应该是可变参

// c++ insights的结果更容易看出实现
#include <iostream>
#include <string>
#include <functional>using namespace std;void Say(std::basic_string<char> str)
{std::operator<<(std::cout, str).operator<<(std::endl);
}template<class Fty>
class myFunction
{
};/* First instantiated from: insights.cpp:31 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class myFunction<void (std::basic_string<char>)>
{public: using pFunc = void (*)(std::basic_string<char>);inline myFunction(pFunc func): func_{func}{}inline void operator()(std::basic_string<char> arg){return this->func_(std::basic_string<char>(arg));}private: pFunc func_;public: 
};#endiftemplate<class R, class Arg>
class myFunction<R (Arg)>
{public: using pFunc = R (*)(Arg);inline myFunction(pFunc func): func_{func}{}inline R operator()(Arg arg){return this->func_(arg);}private: pFunc func_;
};int main()
{myFunction<void (std::basic_string<char>)> func = myFunction<void (std::basic_string<char>)>(Say);func.operator()(std::basic_string<char>("hello world", std::allocator<char>()));return 0;
}

lambda

语法格式:

[]<class T>()->{};

[]:捕获列表

<class T>:c++20新增的,也就是lambda表达式模板

():函数参数列表

->:函数返回类型,c++11的语法,后置返回类型,也有叫尾置返回

{}:函数体

注意,有一个分号

再注意,lambda表达式模板是c++20的语法,此前的c++标准是没有的

lambda表达式的实现原理其实就是模板类,重载了()运算符

例子

从hello开始
#include <iostream>
#include <functional>
#include <string>using namespace std;void Say(string str)
{cout << str << endl;
}int main()
{auto res = []()->void { cout << "hello" << endl; };res();return 0;
}
// c++ inights的结果
#include <iostream>
#include <functional>
#include <string>using namespace std;void Say(std::basic_string<char> str)
{std::operator<<(std::cout, str).operator<<(std::endl);
}int main()
{class __lambda_14_13{public: inline /*constexpr */ void operator()() const{std::operator<<(std::cout, "hello").operator<<(std::endl);}using retType_14_13 = auto (*)() -> void;inline constexpr operator retType_14_13 () const noexcept{return __invoke;};private: static inline /*constexpr */ void __invoke(){__lambda_14_13{}.operator()();}};__lambda_14_13 res = __lambda_14_13{};res.operator()();return 0;
}

可以看到,lambda本质就是一个模板类 + 重载()运算符

带函数参数的lambda
#include <iostream>
#include <functional>
#include <string>using namespace std;void Say(string str)
{cout << str << endl;
}int main()
{auto res = [](int a, int b)->int { return a + b; };cout << res(1, 2) << endl;return 0;
}
捕获外界变量的lambda
#include <iostream>
#include <functional>
#include <string>using namespace std;void Say(string str)
{cout << str << endl;
}int val;int main()
{int a = 0, b = 0;cin >> a >> b;// 值捕获auto res = [a, b]()->int { return a + b; };cout << res() << endl;// 引用捕获auto ans = [&a, &b]()->int { a++, b++; return a + b; };cout << ans() << endl;// 值捕获,但是捕获当前作用域所有变量:局部和全局auto cnt = [=]()->int { return val + b; };cout << cnt() << endl;// 引用捕获,但是捕获当前作用域所有变量:局部和全局auto tmp = [&]()->int { return val + b; };cout << tmp() << endl;return 0;
}

关于捕获列表,还有其他花活,比如说捕获this指针,值捕获,但是某些变量引用捕获以及一个比较重要的:值捕获,但是可以修改捕获的变量(mutable),有兴趣可以自行了解

至于尾置返回类型,是可以省略的,如果省略了,就默认返回void

本文标签: 现代c急急急functionalbindlambda