modern C++:闭包与匿名函数

最近放假在写一个小项目,用到了闭包和匿名函数的知识,记录一下

What?

匿名函数:匿名函数(英语:Anonymous Function)在计算机编程中是指一类无需定义标识符(函数名)的函数或子程序,普遍存在于多种编程语言中。C++从C++11开始支持。
闭包:闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持函数编程的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)

匿名函数只是一个没有名字的函数,闭包是一个函数指针+配套环境,闭包是一个函数的实例。
当匿名函数内也有外界定义的变量时就变成了闭包,当闭包没有外界变量时可以优化成一个普通函数(不一定是匿名函数)

why?

闭包的存在是为了保护私有变量不被污染,形成不销毁的栈内存,里面的私有变量等信息保存下来。
匿名函数常用于回调函数、事件处理器、或者在需要临时定义函数的地方。它们有助于减少代码的复杂性,使代码更加简洁。

how?

匿名函数是一种技术,只有lambda表达式一种实现。
闭包是一种思想,可以用不同方式来实现

方法一:重载()操作符

C++允许进行操作符重载,可以将一个类的()操作符重载,就可以使这个类的实例可以调用,Eg:

#include <iostream>

class Operator {
    public:
        int operator ()(int a, int b) {
            return a+b+bias;
        }
    private:
        int bias = 1;
};

int main() {
    Operator op;
    std::cout << op(1, 2) << std::endl;

    return 0;
}

好奇,能不能将这个重载声明为static,试了一下发现报错说运算符不能是静态成员函数。

方法二:lambda表达式

C++11中引入了lambda表达式,lambda是函数式编程中的概念,用于定义匿名函数,Eg:

#include <iostream>
#include <functional>

int main() {
    int bias = 1;
    std::function<int(int)> f = [bias](int x) { return x + bias; };
    // lambda函数,bias是捕获的变量,x是参数,
    // 如果不捕获变量,可以写成[=],如果捕获所有变量,可以写成[&]
    // 不捕获变量时,就是一个普通的函数,捕获后就变成了闭包
    std::cout << f(1) << std::endl;
    return 0;
}

方法三:参数绑定

C++11在标准库中新增了bind函数,std::bind是C++中实现函数参数绑定的一种方式,它允许你创建一个可调用的函数对象,该对象可以存储一部分参数,并在需要时再提供剩余的参数。这在某些情况下非常有用,比如在多线程编程中,你可能需要将参数和函数传递给不同的线程。

C++11之前boost中有参数绑定函数bind,可以调用boost::bind函数

使用bind定义闭包如下:

#include <iostream>
#include <functional>

int add(int a, int b) {
    std::cout << "a: " << a << " b: " << b << std::endl;
    return a + b;
}

int main()
{
    auto f = std::bind(add, std::placeholders::_2, std::placeholders::_1);
    // placeholders可以理解为占位符,_1表示第一个参数,_2表示第二个参数,以此类推
    // 上面的代码表示将add函数的第二个参数作为f的第一个参数,第一个参数作为f的第二个参数
    // 所以调用f(1,2)时,实际上调用的是add(2,1)
    std::cout << f(1,2) << std::endl;

    auto f2 = std::bind(add, 1, std::placeholders::_1);
    // 上面的代码表示将add函数的第一个参数固定为1,第一个参数作为f2的第二个参数
    std::cout << f2(2) << std::endl;

    auto f3 = std::bind(add, std::placeholders::_1, 2);
    // 上面的代码表示将add函数的第二个参数固定为2,第一个参数作为f3的第一个参数
    std::cout << f3(1) << std::endl;

    auto f4 = std::bind(add, 1, std::placeholders::_2);
    // 上面的代码表示将add函数的第一个参数固定为1,第二个参数作为f4的第二个参数
    // 所以调用时必须给两个参数
    std::cout << f4(1,2) << std::endl;
    return 0;
}

使用时要注意C++不会因为闭包而延长变量的生命周期,在lambda中引用一个已经释放了的变量是一种未定义行为,文档原话:
If a non-reference entity is captured by reference, implicitly or explicitly, and operator() of the closure object is invoked after the entity’s lifetime has ended, undefined behavior occurs. The C++ closures do not extend the lifetimes of objects captured by reference.

others

  • C语言
    C语言中没有语法可以支持闭包,但是C语言支持回调函数,可以通过回调函数来达到类似于闭包的效果。
    在C语言中,支持回调函数的库有时在注册时需要两个参数:一个函数指针,一个独立的void*指针用以保存用户数据。这样的做法允许回调函数恢复其调用时的状态。这样的惯用法在功能上类似于闭包,但语法上有所不同。
  • Python
    Python从一开始就支持闭包,在Python中经常用闭包来实现装饰器
import functools
import time

def logger(func):
    """装饰器函数,用于记录函数的调用信息"""
    @functools.wraps(func)  # 保持原始函数的名称和文档字符串
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} at {time.ctime()}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned {result}")
        return result
    return wrapper

# 使用装饰器
@logger
def add(a, b):
    """Add two numbers."""
    return a + b

# 调用被装饰的函数
result = add(3, 4)
  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值