C++ lambda 使用引用捕获局部变量的陷阱
文章目录
【注意】最后更新于 November 9, 2020,文中内容可能已过时,请谨慎使用。
C++ NB $\times$
C++ UB $\sqrt{}$
这是一篇流水帐。
学JS
前一天,我学习了 JavaScript 中的闭包。
写题
当天,我在写一道需要使用 SAM 的题。
想写闭包
我想到了昨天学的闭包,于是想“要是用 JS 就能写一个优美的迭代器用于访问 SAM 了”。
(想象中的代码:)
function<int(char)> makeIterator()
{
int cur = 0;
return [this, &cur](char nxt) { return cur = t[cur].ch[nxt - 'a']; };
}
测试了一下,发现它 work 了!
(Arch Linux,g++ 10.1.0,-std=c++11 -O2
)
auto makeCounter = []
{
int cnt = 0;
return [&cnt] { return ++cnt; };
};
auto counter1 = makeCounter();
auto counter2 = makeCounter();
wtb(counter1());
wtb(counter1());
wtb(counter2());
wtb(counter1());
wtb(counter2());
wtb(counter2());
输出:
1
2
1
3
2
3
发现函数返回时还是会调用析构函数
#include <iostream>
#include <functional>
using namespace std;
class Test
{
public:
Test(int id) : m_id(id), m_value(new int(0)) { cout << "constructor: " << m_id << endl; }
~Test() { cout << "destructor: " << m_id << endl; }
void add(int delta) { *m_value += delta; }
int value() const { return *m_value; }
private:
int m_id;
int *m_value;
};
int main()
{
auto makeCounter = [](int id) {
Test cnt(id);
return [&cnt] {
cnt.add(1);
return cnt.value();
};
};
auto counter1 = makeCounter(1);
auto counter2 = makeCounter(2);
cout << counter1() << endl;
cout << counter1() << endl;
cout << counter2() << endl;
cout << counter1() << endl;
return 0;
}
输出:
constructor: 1
destructor: 1
constructor: 2
destructor: 2
1
2
1
3
甚至会轻而易举地 UB
(其它代码不变)
auto makeCounter = [](int id) {
Test cnt(id);
auto ret = [&cnt] {
cnt.add(1);
return cnt.value();
};
cnt.add(1);
return ret;
};
constructor: 1
destructor: 1
constructor: 2
destructor: 2
2
-72537979
-72537978
-72537977
甚至 最开始 那个版本,只要不开 O2 就会
输出:
1
2
3
4
5
6
发现有博客讲这个
Modern C++中lambda表达式的陷阱_尘中远的程序开发记录
得出结论
当你以为 C++ NB 的时候,实际上它可能在 UB。
C++ 的局部变量确实是会在函数返回/语句块结束时销毁的,但销毁后由于 UB 可能还能用(
总之不要在局部变量销毁后还能执行的 lambda 中以引用捕获局部变量。
评论正在加载中...如果评论较长时间无法加载,你可以 搜索对应的 issue 或者 新建一个 issue 。