Lambda函数中向std::vector批量添加元素触发读访问违例的问题及解决
问题现象
运行代码时触发读访问违例:
Exception thrown: read access violation.
_My_data was 0xFFFFFFFFFFFFFFE7.
当在Lambda函数中循环向std::vector<Bullet>添加10个Bullet对象时出现该异常,但仅添加1个对象时可正常运行。
触发异常的代码
b.setOnAction([&](Bullet* b) { for (int j = 0; j < 10; j++) { Bullet b1; b1.init(); bullets.push_back(b1); } } );
可正常运行的代码
b.setOnAction([&](Bullet* b) { Bullet b1; b1.init(); bullets.push_back(b1); } );
Bullet类定义
setOnAction用于为Bullet类内部的std::function<void(Bullet*)>类型变量赋值,该变量会在update函数中被调用,类定义如下:
class Bullet { private: std::function<void(Bullet*)> mOnAction; public: void setOnAction(std::function<void(Bullet*)> func) { mOnAction = func; }; void init() { mOnAction = [](Bullet* b) {}; } void update() { mOnAction(this); } };
main函数代码
bullets是存储Bullet对象的std::vector,main函数代码如下:
int main() { std::vector<Bullet> bullets; for (int i = 0; i < 10; i++) { Bullet b; b.init(); b.setOnAction([&](Bullet* b) { for (int j = 0; j < 10; j++) { Bullet b1; b1.init(); bullets.push_back(b1); } } ); bullets.push_back(b); } while (true) { for (int i = bullets.size() - 1; i >= 0; i--) { Bullet& b = bullets[i]; b.update(); } } return 0; }
问题原因
std::vector的push_back操作在容器容量不足时,会重新分配一块更大的内存空间,并将原有元素拷贝/移动到新内存中,这会导致原有元素的引用、迭代器全部失效。
在main函数的循环中,通过Bullet& b = bullets[i]获取了容器中元素的引用,随后调用b.update()触发Lambda执行,而Lambda中多次调用bullets.push_back(b1)很容易触发vector扩容,此时之前获取的b引用就变成了悬空引用。后续访问该引用指向的mOnAction时,就会触发读访问违例。
仅添加1个对象时,vector可能刚好未达到扩容阈值,因此引用未失效,代码可正常运行。
解决方法
通过创建临时std::vector<Bullet>先存储新创建的Bullet对象,在遍历主容器完成后,再批量将临时容器中的元素转移到主容器中,避免在遍历过程中修改主容器导致引用失效。代码示例如下:
// 临时容器,与主容器bullets并列创建 std::vector<Bullet> bulletsToAdd; b.setOnAction([&](Bullet* b) { for (int j = 0; j < 10; j++) { Bullet b1; b1.init(); // 将新对象添加到临时容器 bulletsToAdd.push_back(b1); } } ); while (true) { // 将临时容器中的所有元素批量添加到主容器 for (auto& b : bulletsToAdd) bullets.push_back(b); // 清空临时容器,准备下一轮使用 bulletsToAdd.clear(); for (int i = bullets.size() - 1; i >= 0; i--) { Bullet& b = bullets[i]; b.update(); } }
内容的提问来源于stack exchange,提问作者Momoyon

