《Effective Modern C++》学习笔记之条款三十一:避免默认捕获模式

我们知道C++11中,lambda有两种默认捕获方式:按值或者按引用。

其中按引用的默认捕获方式存在可能的空悬引用问题,例如在lambda按引用捕获了某局部变量的引用,一旦lambda表达式创建的闭包越过了该局部变量的使命周期,则闭包内的该引用将会空悬。

考虑如下代码,我们有一个元素为筛选器的vector容器,每个筛选器都接受一个int参数,返回值为bool类型,表示传入的值是否满足筛选条件:

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

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

上述代码随时会出错,因为该lambda式指涉到了局部变量diviisor的引用,diviisor变量的声明周期仅仅是addDivisorFilter内,当addDivisorFilter()函数返回时,diviisor将被释放,而之后再使用该lambda表达式将会导致未定义的行为。

当然,如果你确定lambda创建的闭包的生命周期,一定小于它所引用的局部变量或形参,那按引用捕获不会产生任何问题。

如果要解决上述问题,一个办法就是使用按值捕获的方式:

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类的成员变量,理论上它压根无法捕获,而之所以又可以编译通过,是因为lambda捕获了Widget对象的this指针,上面代码用到的divisor实际是this->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};    // 使用副本
    )
}

C++14中捕获成员变量有一个更好的方法叫做广义lambda捕获,代码如下:

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chiang木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值