函数对象
实际上就是类中重载了(),以至于使用类对象能像调用函数一样。
先看个最基础的了解一下怎么使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <iostream>
class Adder { public: Adder() = default;
int operator()(int a, int b) { return a + b; } };
int main() { Adder add; std::cout << add(1, 1) << '\n'; std::cout << add(2, 3) << '\n';
return 0; }
|
可以看到,就是把对象当做函数调用。
lambda函数是匿名函数对象的语法糖
函数对象可以结合泛型编程使用,这很正常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <iostream> #include <algorithm> #include <array> #include <vector>
class ListArr { public: ListArr() = default;
template <typename ArrT> void operator()(ArrT& arr) { std::for_each(arr.begin(), arr.end(), [](const auto& e) { std::cout << e << ' '; } ); std::cout << std::endl; } };
int main() { std::vector<int> a {1, 2, 3}; std::vector<double> b {4.5, 5.5, 6.5}; std::array<int, 3> c {7, 8, 9};
ListArr la; la(a); la(b); la(c);
return 0; }
|
上面的代码用到了c++14支持的lambda泛型,所以需要使用c++14和以上版本的标准编译,现在我基本都使用c++17标准编译自己的代码。
我们上面在函数对象中写了个lambda函数,实际上lambda函数本质上就是个匿名函数对象,lambda函数是个匿名函数对象的语法糖。
我们写的lambda表达式:
1
| [](const auto& e) { std::cout << e << ' '; }
|
编译器生成的匿名函数对象大概是:
1 2 3 4 5 6 7
| class __AnonymousLambda { public: template<typename T> void operator()(const T& e) const { std::cout << e << ' '; } };
|
实际上编译器会生成更完整的内容(局部类或者结构体),让我们的调用能够生效。
函数对象利于内联优化
众所周知,编译器内联优化就是把函数源码填充到调用的地方,这样我们的代码就少了一些地址跳转,函数返回,达到优化的目的,但这个前提是编译器能够知道调用的源码是什么才行。
倘若我们使用函数指针,那编译器在编译期间基本没法知道函数地址,就没法内联优化,而使用函数对象在编译期间是可以确定内容的,当然,用lambda函数也一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| template <typename Func> void repeat(int n, Func f) { for (int i = 0; i < n; ++i) f(i); }
struct Printer { void operator()(int x) const { std::cout << x << ' '; } };
int main() { repeat(5, Printer()); repeat(5, [](int x){ std::cout << x << ' '; }); }
|
写一个小学二年级都会的斐波那契数列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <iostream>
template <int N> class Fib { public: constexpr int operator()() const { if constexpr (N == 0) return 0; if constexpr (N == 1 || N == 2) { return 1; } else { return Fib<N-1>()() + Fib<N-2>()(); }
} };
int main() { std::cout << Fib<3>()() << '\n'; std::cout << Fib<5>()() << '\n'; std::cout << Fib<7>()() << '\n'; std::cout << Fib<9>()() << '\n';
return 0; }
|
用非类型模板和函数对象可以达到一种诡异的效果,其实也没什么神秘的,就是编译期计算和函数对象调用罢了。
要注意if constexpr是c++17引入的,编译标准需要改到c++17。
上面代码有意思的点在于,去掉{}就会编译错误。
究其原因就是因为这还是在编译期,return之后还是要编译后面的语句,而我们的运算也是在编译期发生的,所以就会发生无限递归,越界。
1 2 3 4 5
| template <int N> constexpr int fib() { if constexpr (N == 1) return 1; return fib<N-1>() + fib<N-2>(); }
|