我在C ++中遇到lambda函数的问题:我正在尝试定义一个异步加载器,它在给定一个字符串列表作为输入的情况下填充一个对象数组。
代码看起来像这样(不完全是,但我希望你能得到这个想法):
void loadData() {
while (we_have_data()) {
std::string str = getNext();
array.resize(array.size() + 1);
element &e = array.back();
tasks.push_back([&, str] () {
std::istringstream iss(str);
iss >> e;
}
}
for (auto task: tasks) {
task();
}
}
当我最后扫描任务列表并执行它们时,应用程序在lambda内第一次访问变量e时崩溃。如果我在调试器中运行,我可以在对象e本身内找到正确的值。我做错了什么,但我真的不明白。
答案 0 :(得分:4)
你持有一个悬垂的参考。当你这样做
tasks.push_back([&, str] () {
std::istringstream iss(str);
iss >> e;
}
您通过引用捕获array.back()
返回的元素,因为对e
的引用实际上是对e
所引用的内容的引用。不幸的是,{while}循环中会调用resize
,所以当array
调整大小时,对back()
的引用会失效,而您现在指的是一个不再存在的对象。
答案 1 :(得分:4)
element& e
的范围是while
- 循环。
在while
- 循环的每次迭代之后,你有lambda函数,并且捕获了对不同e
的引用,这些函数都超出了范围。
答案 2 :(得分:2)
您捕获e
(又名。array.back()
)"按引用"在创建lambda时,随后调整array
的大小(可能会重新分配),留下悬空引用,并在您尝试访问此悬空引用时导致错误。在数组经历调整大小(和重新分配)之后,任何尝试(不限于lambda)通过先前分配的引用访问array
中的元素将导致"悬空引用"问题
替代...而不是两个循环,为什么不立即在while
循环中执行任务并放弃悬空引用并尝试获取基于指针或迭代器的替代方案。
进一步替代......如果可以共享array
中的元素,std::shared_ptr
解决方案可以正常工作,则需要注意的是按值捕获shared_ptr
元素,从而确保
该想法的抽样......
void loadData() {
while (we_have_data()) {
std::string str = getNext();
array.resize(array.size() + 1);
std::shared_ptr<element> e = array.back();
tasks.push_back([e, str] () {
std::istringstream iss(str);
iss >> *e;
}
}
for (auto task: tasks) {
task();
}
}
答案 3 :(得分:0)
你这里有两次罢工。
首先,您正在捕获对迭代器的引用,该迭代器可能会调整大小并因此重新定位。
其次,您正在捕获对超出范围的本地(堆栈)变量的引用。在循环中,编译器可能每次都使用相同的内存位置'e',因此所有引用都指向相同的堆栈位置。
更简单的解决方案是存储元素编号:
while (we_have_data()) {
std::string str = getNext();
size_t e = array.size();
array.resize(e + 1);
tasks.push_back([&, e, str] () {
std::istringstream iss(str);
iss >> array[e];
}
}
如果你有C ++ 14并且字符串很长,你可能需要考虑:
tasks.push_back([&, e, str{std::move(str)}] () {
所有这些都假定在任务运行时阵列不会进行进一步的操作或超出范围。