C++ Boost Phoenix库示例分析使用
作者:无水先生
一、说明
在函数式编程模型中,函数是对象,与其他对象一样,可以作为参数传递给函数或存储在容器中。有许多支持函数式编程模型的 Boost 库。
- Boost.Phoenix 是这些库中最广泛、也是最重要的库。它取代了库 Boost.Lambda,它被简要介绍,但只是为了完整性。
- Boost.Function 提供了一个类,可以轻松定义函数指针,而无需使用源自 C 编程语言的语法。
- Boost.Bind 是一个适配器,即使实际签名与预期签名不同,它也允许您将函数作为参数传递给其他函数。
- Boost.Ref 可用于传递对对象的引用,即使函数通过副本传递参数。
- Boost.Lambda 可以称为 Boost.Phoenix 的前身。它是一个相当古老的库,并且在将 C++11 添加到编程语言之前很多年就允许使用 lambda 函数。
二、预先知道Boost.Phoenix
Boost.Phoenix 是函数式编程最重要的 Boost 库。虽然 Boost.Bind 或 Boost.Lambda 等库为函数式编程提供了一些支持,但 Boost.Phoenix 包含这些库的功能并超越了它们。
在函数式编程中,函数是对象,可以像对象一样处理。使用 Boost.Phoenix,一个函数可以返回另一个函数作为结果。也可以将一个函数作为参数传递给另一个函数。因为函数是对象,所以可以区分实例化和执行。访问一个函数不等于执行它。
Boost.Phoenix 支持使用函数对象进行函数式编程:函数是基于类的对象,这些类重载了运算符 operator()。这样,函数对象的行为就像 C++ 中的其他对象一样。例如,它们可以被复制并存储在容器中。但是,它们的行为也类似于函数,因为它们可以被调用。
函数式编程在 C++ 中并不新鲜。您可以将一个函数作为参数传递给另一个函数,而无需使用 Boost.Phoenix。
三、示例和代码
示例 39.1。谓词作为全局函数、lambda 函数和 Phoenix 函数
#include <boost/phoenix/phoenix.hpp> #include <vector> #include <algorithm> #include <iostream> bool is_odd(int i) { return i % 2 == 1; } int main() { std::vector<int> v{1, 2, 3, 4, 5}; std::cout << std::count_if(v.begin(), v.end(), is_odd) << '\n'; auto lambda = [](int i){ return i % 2 == 1; }; std::cout << std::count_if(v.begin(), v.end(), lambda) << '\n'; using namespace boost::phoenix::placeholders; auto phoenix = arg1 % 2 == 1; std::cout << std::count_if(v.begin(), v.end(), phoenix) << '\n'; }
示例 39.1 使用算法 std::count_if() 来计算向量 v 中的奇数。std::count_if() 被调用 3 次,一次使用谓词作为独立函数,一次使用 lambda 函数,一次使用凤凰功能。
Phoenix 函数与独立函数和 lambda 函数不同,因为它没有框架。虽然其他两个函数有一个带有签名的函数头,但 Phoenix 函数似乎只包含一个函数体。
Phoenix 函数的关键组件是 boost::phoenix::placeholders::arg1。 arg1 是函数对象的全局实例。您可以像 std::cout 一样使用它:一旦包含相应的头文件,这些对象就会存在。
arg1 用于定义一元函数。表达式 arg1 % 2 == 1 创建一个需要一个参数的新函数。该函数不会立即执行,而是存储在 Phoenix 中。 phoenix 被传递给 std::count_if() ,它为 v 中的每个数字调用谓词。
arg1 是调用 Phoenix 函数时传递的值的占位符。由于此处仅使用了 arg1,因此创建了一个一元函数。 Boost.Phoenix 提供了额外的占位符,例如 boost::phoenix::placeholders::arg2 和 boost::phoenix::placeholders::arg3。 Phoenix 函数总是期望与具有最大数量的占位符一样多的参数。
示例 39.1 将 3 写入标准输出 3 次。
示例 39.2。 Phoenix 函数与 lambda 函数
#include <boost/phoenix/phoenix.hpp> #include <vector> #include <algorithm> #include <iostream> int main() { std::vector<int> v{1, 2, 3, 4, 5}; auto lambda = [](int i){ return i % 2 == 1; }; std::cout << std::count_if(v.begin(), v.end(), lambda) << '\n'; std::vector<long> v2; v2.insert(v2.begin(), v.begin(), v.end()); using namespace boost::phoenix::placeholders; auto phoenix = arg1 % 2 == 1; std::cout << std::count_if(v.begin(), v.end(), phoenix) << '\n'; std::cout << std::count_if(v2.begin(), v2.end(), phoenix) << '\n'; }
例 39.2 强调了 Phoenix 和 lambda 函数之间的一个关键区别。除了不需要带有参数列表的函数头之外,Phoenix 函数参数没有类型。 lambda 函数 lambda 需要一个 int 类型的参数。 Phoenix 函数 phoenix 将接受模运算符可以处理的任何类型。
将 Phoenix 函数视为函数模板。像函数模板一样,Phoenix 函数可以接受任何类型。这使得在示例 39.2 中可以使用 phoenix 作为容器 v 和 v2 的谓词,即使它们存储不同类型的数字。如果您尝试将谓词 lambda 与 v2 一起使用,则会出现编译器错误。
示例 39.3。 Phoenix 用作延迟 C++ 代码
#include <boost/phoenix/phoenix.hpp> #include <vector> #include <algorithm> #include <iostream> int main() { std::vector<int> v{1, 2, 3, 4, 5}; using namespace boost::phoenix::placeholders; auto phoenix = arg1 > 2 && arg1 % 2 == 1; std::cout << std::count_if(v.begin(), v.end(), phoenix) << '\n'; }
示例 39.3 使用 Phoenix 函数作为谓词,使用 std::count_if() 计算大于 2 的奇数。Phoenix 函数访问 arg1 两次:一次测试占位符是否大于 2,一次测试它是否为奇数.条件与 && 相关联。
您可以将 Phoenix 函数视为不会立即执行的 C++ 代码。示例 39.3 中的 Phoenix 函数看起来像一个使用多个逻辑和算术运算符的条件。但是,条件不会立即执行。它仅在从 std::count_if() 中访问时执行。 std::count_if() 中的访问是正常的函数调用。
示例 39.3 将 2 写入标准输出。
示例 39.4。显式 Phoenix 类型
#include <boost/phoenix/phoenix.hpp> #include <vector> #include <algorithm> #include <iostream> int main() { std::vector<int> v{1, 2, 3, 4, 5}; using namespace boost::phoenix; using namespace boost::phoenix::placeholders; auto phoenix = arg1 > val(2) && arg1 % val(2) == val(1); std::cout << std::count_if(v.begin(), v.end(), phoenix) << '\n'; }
例 39.4 对 Phoenix 函数中的所有操作数使用显式类型。严格来说,你看不到类型,只有辅助函数 boost::phoenix::val()。此函数返回使用传递给 boost::phoenix::val() 的值初始化的函数对象。函数对象的实际类型无关紧要。重要的是 Boost.Phoenix 为不同类型重载了运算符,如 >、&&、% 和 ==。因此,不会立即检查条件。相反,函数对象被组合以创建更强大的函数对象。根据操作数,它们可能会自动用作函数对象。否则,您可以调用 val() 等辅助函数。
示例 39.5。 boost::phoenix::placeholders::arg1 和 boost::phoenix::val()
#include <boost/phoenix/phoenix.hpp> #include <iostream> int main() { using namespace boost::phoenix::placeholders; std::cout << arg1(1, 2, 3, 4, 5) << '\n'; auto v = boost::phoenix::val(2); std::cout << v() << '\n'; }
示例 39.5 说明了 arg1 和 val() 如何工作。 arg1 是函数对象的实例。它可以直接使用,也可以像函数一样调用。您可以传递任意数量的参数——arg1 返回第一个参数。
val() 是一个用于创建函数对象实例的函数。函数对象使用作为参数传递的值进行初始化。如果实例像函数一样被访问,则返回该值。
示例 39.5 将 1 和 2 写入标准输出。
示例 39.6。创建自己的 Phoenix 函数
#include <boost/phoenix/phoenix.hpp> #include <vector> #include <algorithm> #include <iostream> struct is_odd_impl { typedef bool result_type; template <typename T> bool operator()(T t) const { return t % 2 == 1; } }; boost::phoenix::function<is_odd_impl> is_odd; int main() { std::vector<int> v{1, 2, 3, 4, 5}; using namespace boost::phoenix::placeholders; std::cout << std::count_if(v.begin(), v.end(), is_odd(arg1)) << '\n'; }
示例 39.6 解释了如何创建自己的 Phoenix 函数。您将函数对象传递给模板 boost::phoenix::function。该示例传递了 is_odd_impl 类。此类重载运算符 operator():当传入奇数时,运算符返回 true。否则,运算符返回 false。
请注意,您必须定义类型result_type。 Boost.Phoenix 使用它来检测运算符 operator() 的返回值的类型。
is_odd() 是一个可以像 val() 一样使用的函数。两个函数都返回一个函数对象。调用时,参数将转发给运算符 operator()。对于示例 39.6,这意味着 std::count_if() 仍然计算奇数。
示例 39.7。将独立功能转换为 Phoenix 功能
#include <boost/phoenix/phoenix.hpp> #include <vector> #include <algorithm> #include <iostream> bool is_odd_function(int i) { return i % 2 == 1; } BOOST_PHOENIX_ADAPT_FUNCTION(bool, is_odd, is_odd_function, 1) int main() { std::vector<int> v{1, 2, 3, 4, 5}; using namespace boost::phoenix::placeholders; std::cout << std::count_if(v.begin(), v.end(), is_odd(arg1)) << '\n'; }
如果要将独立函数转换为 Phoenix 函数,可以按照示例 39.7 进行操作。您不必像前面的示例中那样定义函数对象。
您可以使用宏 BOOST_PHOENIX_ADAPT_FUNCTION 将独立函数转换为 Phoenix 函数。将返回值的类型、要定义的 Phoenix 函数的名称、独立函数的名称以及参数个数传递给宏。
示例 39.8。 Phoenix 使用 boost::phoenix::bind() 函数
#include <boost/phoenix/phoenix.hpp> #include <vector> #include <algorithm> #include <iostream> bool is_odd(int i) { return i % 2 == 1; } int main() { std::vector<int> v{1, 2, 3, 4, 5}; using namespace boost::phoenix; using namespace boost::phoenix::placeholders; std::cout << std::count_if(v.begin(), v.end(), bind(is_odd, arg1)) << '\n'; }
要将独立函数用作 Phoenix 函数,您还可以使用 boost::phoenix::bind(),如示例 39.8 中所示。 boost::phoenix::bind() 的工作方式类似于 std::bind()。独立函数的名称作为第一个参数传递。所有进一步的参数都被转发到独立功能。
小费
避免使用 boost::phoenix::bind()。创建您自己的 Phoenix 函数。这会导致代码更具可读性。尤其是对于复杂的表达式,处理 boost::phoenix::bind() 的额外细节是没有帮助的。
示例 39.9。任意复杂的 Phoenix 函数
#include <boost/phoenix/phoenix.hpp> #include <vector> #include <algorithm> #include <iostream> int main() { std::vector<int> v{1, 2, 3, 4, 5}; using namespace boost::phoenix; using namespace boost::phoenix::placeholders; int count = 0; std::for_each(v.begin(), v.end(), if_(arg1 > 2 && arg1 % 2 == 1) [ ++ref(count) ]); std::cout << count << '\n'; }
Boost.Phoenix 提供了一些模拟 C++ 关键字的函数对象。例如,您可以使用函数 boost::phoenix::if_()(参见示例 39.9)创建一个函数对象,该对象的行为类似于 if 并测试条件。如果条件为真,则使用 operator[] 传递给函数对象的代码将被执行。当然,该代码也必须基于函数对象。这样,您可以创建复杂的 Phoenix 函数。
示例 39.9 对每个大于 2 的奇数进行增量计数。要对 count 使用增量运算符,请使用 boost::phoenix::ref() 将 count 包装在一个函数对象中。与 boost::phoenix::val() 相比,没有值被复制到函数对象中。 boost::phoenix::ref() 返回的函数对象存储了一个引用——这里是对 count 的引用。
小提示
不要使用 Boost.Phoenix 创建复杂的函数。最好使用 C++11 中的 lambda 函数。虽然 Boost.Phoenix 接近 C++ 语法,但使用 if_ 等关键字或方括号之间的代码块并不一定会提高可读性。
到此这篇关于C++ Boost Phoenix库示例分析使用的文章就介绍到这了,更多相关C++ Boost Phoenix库内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!