Lambda Conf上有人讲C++函数式编程。
在Functional Conf 2019上,就有主题为“Lambdas: The Functional Programming Companion of Modern C++”的演讲。
演讲者介绍了现代C++中函数式编程相关内容,讲解了如何使用Lambda表达式编写符合函数式编程原则的C++代码,以及C++语言的演变等内容。
NDC Conferences上也有相关演讲,主题为“Functional C++ for Fun & Profit”。
演讲探讨了函数式编程与C++的交集,从C++11中Lambda表达式的引入开始,研究了函数式编程的核心原则,以及如何在C++开发中有效应用这些原则。
函数式编程
函数式编程是一种编程范式,核心思想是将计算过程视为数学函数的组合,强调函数的纯粹性和数据的不可变性,避免副作用(如修改外部状态、改变变量值等)。
特点包括:
- 不可变数据:数据一旦创建就不能被修改,若需变更,需生成新的数据副本,减少状态混乱。
- 纯函数:输入决定输出,不依赖外部状态,也不产生副作用(如修改全局变量、IO操作等),相同输入始终返回相同结果,便于测试和并行计算。
- 函数是一等公民:函数可以像变量一样被传递、赋值、作为参数或返回值,支持高阶函数(如接收或返回其他函数)。
- 避免状态变化:通过函数组合而非循环或状态修改来实现逻辑,常用递归代替迭代。
例如,在处理列表时,函数式编程会用 map (映射)、 filter (过滤)等纯函数组合操作,而非通过循环修改列表元素。
常见的函数式编程语言有Haskell、Scala、Erlang等,现代主流语言(如Java、C++、Python)也逐渐引入了函数式特性(如Lambda表达式、不可变集合)。
代码示例
用现代C++特性(Lambda、算法库)处理数据,体现“用函数组合实现逻辑”“避免修改状态”的特点:
#include <iostream>
#include <vector>
#include <algorithm> // 提供函数式算法
#include <numeric> // 提供accumulate
int main() {
// 原始数据(不可修改,体现不可变性)
const std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 1. 过滤:保留偶数(用lambda作为筛选条件)
std::vector<int> evens;
std::copy_if(numbers.begin(), numbers.end(),
std::back_inserter(evens),
[](int n) { return n % 2 == 0; }); // 纯函数:输入决定输出
// 2. 转换:偶数乘以2(用lambda映射)
std::vector<int> doubled;
std::transform(evens.begin(), evens.end(),
std::back_inserter(doubled),
[](int n) { return n * 2; }); // 无副作用的转换
// 3. 聚合:计算总和(用accumulate组合结果)
int sum = std::accumulate(doubled.begin(), doubled.end(),
0, // 初始值
[](int total, int n) { return total + n; });
// 输出结果:2+4+6+8+10的两倍之和 → (2+4+6+8+10)*2 = 60
std::cout << "结果: " << sum << std::endl; // 仅此处有副作用(IO)
return 0;
}
函数式特点说明:
- 不可变数据: numbers 被声明为 const ,全程不修改原始数据。
- 纯函数:所有Lambda表达式(如 n%2==0 、 n*2 )都只依赖输入,无外部状态。
- 函数作为参数: copy_if 、 transform 等算法接收Lambda函数作为参数,体现“函数是一等公民”。
- 无显式循环:用标准库算法替代 for 循环,逻辑更接近“做什么”而非“怎么做”。
运行结果为 60 ,整个过程通过函数组合完成数据处理,避免了手动修改变量状态。
优缺点
好处
1. 代码更易理解和维护
纯函数无副作用,逻辑独立,相同输入始终返回相同结果,代码意图更清晰,减少了因状态变化导致的“隐藏逻辑”。
2. 天然支持并发和并行
不可变数据避免了多线程中的“资源竞争”(无需加锁),纯函数可安全地在多线程中并行执行,适合分布式和高性能场景。
3. 便于测试和调试
纯函数不依赖外部状态,单元测试时无需复杂的环境准备,只需验证输入输出;调试时也无需追踪变量的状态变化历史。
4. 代码复用性强
函数作为“一等公民”可被灵活组合(如通过 map / filter / reduce ),形成新的功能,减少重复代码。
5. 减少错误
不可变性避免了意外修改数据导致的bug(如“一个地方改了,别处跟着错”),尤其在复杂系统中更明显。
坏处
1. 学习曲线较陡
思维方式与常见的命令式编程(如用 for 循环、修改变量)差异大,理解递归、函数组合、Monad等概念需要时间。
2. 性能开销可能更高
不可变数据每次修改需创建副本,对于大型数据(如大列表)可能增加内存占用和计算成本(尽管现代语言有优化)。
3. 处理IO等副作用时较繁琐
纯函数禁止IO(如读写文件、网络请求),需通过特殊机制(如Haskell的IO Monad)封装副作用,代码可能更复杂。
4. 不适合所有场景
对于需要频繁修改状态的场景(如GUI交互、游戏帧更新),函数式的“不可变”思路可能反直觉,实现效率低。
5. 调试栈可能更复杂
大量函数组合和递归可能导致调用栈较深,调试时追踪问题源头相对麻烦。
总结
函数式编程适合复杂业务逻辑、高并发、分布式系统等场景,能提升代码可靠性和可维护性;但在性能敏感、IO密集、状态频繁变化的场景中,可能不如命令式编程直接高效。现代主流语言(如C++、Python)多采用“混合范式”,兼顾两者优势。