条款31:避免默认捕获模式

C++11中有两种默认捕获方式:按值或者按引用。按值的默认捕获方式可能会忽悠你,按引用的默认捕获方式可能导致空悬引用。

按引用捕获会导致闭包包含涉及到局部变量的引用,或者涉及到定义lambda式子的作用域内形参的引用,考虑如下代码:

using FilterContainer = std::vector<std::function<bool(int)>>;
FilterContainer filters;

void addDivisorFilter() {
	auto calc1 = computeSomeValue1();
	auto calc2 = computeSomeValue2();
	
	auto divisor = computeDivisor(calc1, calc2);	
	
    filters.emplace_back(					// 危险,对divisor的指涉可能空悬!
    	[&](int value){ return value % divisor == 0;}
    );
}

上述代码随时会出错,lambda式指涉到局部变量diviisor的引用,但该变量在addDivisorFilter返回时即不存在。就算不这样做,换作以显示方式按引用捕获divisor,问题依旧

filters.emplace_back(					// 危险,对divisor的指涉可能空悬!
    [&divisor](int value){ return value % divisor == 0;}

解决这个问题的一个办法就是使用按值捕获的方式:

filters.emplace_back(					
    [=](int value){ return value % divisor == 0;}

对于本例来说足够;但是问题有:按值捕获后一个指针后,在lambda式子创建的闭包中持有的是这个指针的副本,但是你并无法阻止lambda以外的代码针对该指针实施delete操作所导致的指针副本空悬;

考虑如下代码:

class widget {
public:
    void addFilter() const;

private:
    int divisor;
};

void widget::addFilter() const
{
    filters.emplace_back([=](int value){ return value % divisor == 0;});
}

捕获只能针对在创建lambda表达式的作用域可见的非静态局部变量(包括形参)。在wdiget::addFilter的函数体内,divisor并非局部变量,而是widget类的成员变量。它压根无法捕获。这么一来,如果默认捕获模式消除,代码久无法通过编译

void widget::addFilter() const
{
    filters.emplace_back([](int value){ return value % divisor == 0;}); // 错误!没有可捕获的divisor
}

如果试图显示捕获divisor,这个捕获语句不能通过编译,因为divisor既不是局部变量,也不是形参

void widget::addFilter() const
{
    filters.emplace_back([divisor](int value){ return value % divisor == 0;}); // 错误!没有可捕获的divisor
}

考虑如下代码:

using FilterContainer = std::vector<std::function<bool(int)>>;
FilterContainer filters;

void doSomeWork() {
	auto pw = std::make_unique<Widget>();
	pw->addFilter();
	// ... 			Widget被销毁,filter现在持有空悬指针
}

调用doSomeWork创建了一个筛选函数,依赖于std::make_unique创建的Widget对象,该函数被到filters中,不过当doSomeWork执行结束后,Widget对象即被管理着它的生命周期的std::unique_ptr销毁。从那刻起,filter中就含一个带有空悬指针的元素。这个问题可以通过將你想捕获的成员变量复制到局部变量中,然后捕获该局部变量解决:

void Widget::addFilter() const {
	auto divisorCopy = divisor; // 复制成员变量
	filter.emplace_back(
		[divsorCopy](int value)	// 捕获副本
		{ return value % divisorCopy == 0};	// 使用副本
	)
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值