《Effective Modern C++》学习笔记 - Item 32: 使用初始化捕获将对象移动至lambda函数中

本文探讨了C++11和C++14中lambda函数捕获参数的不同方式。C++11仅支持按值和按引用捕获,不适用于移动构造的参数,而C++14引入了初始化捕获,允许使用表达式初始化lambda参数,解决了这一问题。在C++11下,可以通过自定义仿函数或std::bind实现类似效果。文章提供了相关示例并比较了不同方法的适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • C++11 中 lambda 函数捕获参数的语义只有两种:按值捕获 [=] 和按引用捕获 [&],而且只能声明变量名,不支持表达式。这导致了一个问题:无法使用移动构造的参数。这对于拷贝成本高而移动成本低的对象(如STL中的大部分容器)以及只能移动构造的对象(如 std::unique_ptrstd::future)是很不友好的。

  • C++14 对此做了改进,引入了一种新的捕获机制:初始化捕获(init capture),或者叫广义 lambda 捕获(generalized lambda capture),允许我们以 [param = expr] 的形式使用右侧的表达式来初始化左侧的 lambda 函数的参数。例如:

class Widget { // some useful type
...
}
...
auto pw_var = std::make_unique<Widget>();		// std::unique_ptr
												// is move-only type
// configure pw_var...
auto func = [pw_param = std::move(pw_var)]{		// pw_var is move-constructed
    return pw_param == nullptr;					// into pw_param
};
func();

实际上等号右侧可以是任意表达式,因此上例中如果不需要在创建 func 前对 pw_var 做任何操作,也可以写成:[pw_param = std::make_unique<Widget>()]

  • 如果不能使用 C++14,使用 C++11 也有解决办法。具体来说有两种:一是使用自定义类(仿函数),二是使用 lambda + std::bind

  • 第一种方法:使用仿函数,其实就是C++98时代实现带状态函数的方法。尽管本例不需要用到状态,但我们还是可以借用其灵活性。以上代码等价于:

class WidgetIsNullptr {
public:
    using DataType = std::unique_ptr<Widget>;
    
    IsNullptr(DataType&& ptr) : pw(std::move(ptr)) {}

    bool operator()()
    { return pw == nullptr; }

private:
    DataType pw;
};
...
auto func = WidgetIsNullptr(std::make_unique<Widget>());
func();
  • 第二种方法:使用 std::bindstd::bind 接受一个可调用(Callable)对象 f 和若干个参数 args,返回一个新的可调用对象,调用该对象等价于调用 f(args)。其中 args 既可以包含调用 bind 时立刻给出的参数,也可以包含在调用 bind 产生对象时才给出的参数,使用 std::placeholders 命名空间下的 _1, _2, ... 占位符声明。这里我们把原来的 lambda 函数作为 f,需要移动的对象作为 args 调用 std::bind

    如此看起来只是在 lambda 函数外多套了一层能够实现效果的原理是:bind 产生的对象会存储一份入参的拷贝,由于我们提供的入参是 std::move(data),所以拷贝是通过调用 vector 的移动构造函数实现的。而 lambda 函数按引用接受参数——注意,是参数不是从外部域捕获变量,而这个参数由 std::bind 绑定到了刚才的(通过移动构造的)std::bind 的入参拷贝上,所以在调用 func 时,仍和之前一样不需任何参数。
std::vector<int> data{ 1, 2, 3, 4 };

auto func = std::bind(
    [](const std::vector<int>& data) 
    { /* uses of data */ },
    std::move(data));
func();

尽管在 Item 34 中会说明为何应倾向于使用 lambda 函数而不是 std::bind,这里介绍的的确是 std::bind 很方便的一种场景。当然,如果能用C++14,那么初始化捕获以及 auto 参数类型会消灭这样的场景。

总结

  1. 使用 C++14 的初始化捕获以实现将对象移动至闭包(lambda函数)中。
  2. 在 C++11 中,通过手写类(仿函数)或 std::bind 模仿上面的效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值