修复std::vector无序删除函数的未定义行为且不损失效率的方法
无额外性能损耗的修复方案
原函数的UB本质来源非常明确:根据C++标准,pop_back操作会直接使指向被删除的尾元素的迭代器、以及end()迭代器失效,无论失效的迭代器逻辑位置是否等价于新的end(),只要在失效后对其做解引用、比较等操作,就会触发UB。
而原函数的UB只会出现在单一场景下:当遍历到it == end(vec) - 1(即it指向vector最后一个元素),且该元素值等于待删除值时,后续pop_back会直接让it失效,下一轮循环对it != end(vec)的判断就会使用失效迭代器,触发UB。
针对这个特定场景的修复不需要引入高频的冗余检查,额外开销几乎可以忽略:
- 利用vector随机访问迭代器的特性,在执行删除操作、所有迭代器都处于有效状态时,仅做一次位置判断提前确认it是否为尾元素,再根据判断结果分支处理:
- 如果it不是尾元素:此时
pop_back只会删除it后方的尾元素,it本身不会失效,保持原逻辑执行move替换即可,it指向的移入新元素待后续检查。 - 如果it就是尾元素:此时不需要执行多余的自move赋值(本来就是move自己给自己),直接执行
pop_back,同时直接将it赋值为操作后新的end(vec),从根源上避免使用即将失效的it值。
修复后的实现代码:
- 如果it不是尾元素:此时
template<class T> void remove_unordered(std::vector<T>& vec, T const& val) { auto it = begin(vec); while (it != end(vec)) { if (*it == val) { auto current_end = end(vec); bool is_tail = (it == current_end - 1); if (!is_tail) { // it不在尾端,正常用尾元素替换当前待删除位置 *it = std::move(vec.back()); } vec.pop_back(); if (is_tail) { // it就是被pop掉的尾元素,直接赋值为新的有效end迭代器,避免失效迭代器参与后续判断 it = end(vec); } } else { ++it; } } }
性能说明
- 额外开销仅为每次触发删除时的1次整数比较(vector迭代器的位置本质是数组偏移量,判断it是否为尾元素等价于比较两个整数的大小),和原循环的判断逻辑开销完全一致。
- 相比原函数,该实现还去掉了it为尾元素时多余的自move赋值操作,整体执行效率比原函数略高,不存在此前方案提到的性能损耗问题。
- 完全规避了UB场景:所有参与比较、判断的迭代器都是C++标准定义下的有效迭代器,不存在对失效迭代器的使用。
补充说明:很多此前的修复方案选择在每次循环刷新
end()、或者添加冗余的边界检查,本质是没有精准定位到UB的唯一触发场景,才会引入不必要的高频操作开销。
内容的提问来源于stack exchange,提问作者Henk
相关产品推荐
相关产品推荐

