自定义std::vector::erase()销毁流程 如何触发被擦除元素析构
问题解答
现象原理说明
你观察到的行为是std::vector的固有特性,原因如下:
std::vector的元素在内存中是连续存储的,当你擦除起始位置的区间[0,2)时,必须将区间之后的所有元素向前移动,填补被擦除的位置空缺- 你的自定义
temp类显式声明了拷贝赋值运算符,导致编译器不会自动生成移动赋值运算符,erase操作只能通过拷贝赋值将后面的元素覆盖到待擦除的位置:- 原
vec[0](TYPE0)被vec[2](TYPE2)拷贝赋值覆盖 - 原
vec[1](TYPE1)被vec[3](TYPE3)拷贝赋值覆盖 - 原
vec[2](TYPE2)被vec[4](TYPE4)拷贝赋值覆盖
- 原
- 移动完成后,vector尾部会多出2个冗余的元素(原
vec[3]、vec[4]对应的内存位置),这两个元素会被析构,就是你看到的输出中析构TYPE3、TYPE4的日志 - 而原
[0,2)区间的元素只是被新值覆盖,内存没有被回收,所以不会触发析构调用,直到整个vector销毁时,这两段内存对应的新值才会被析构
解决方案
方案1:改用std::list容器
如果你的场景允许更换容器,std::list是链表结构,擦除指定区间时不需要移动其他元素,会直接析构目标区间的元素,完全符合你的需求,仅需修改容器相关代码即可:
// 头文件添加 #include <list> // 把std::vector<temp> vec; 改成 std::list<temp> vec; // 对应的erase调用修改为: vec.erase(std::next(vec.begin(), start), std::next(vec.begin(), end));
方案2:为temp类添加移动赋值运算符
如果必须使用std::vector,可以给temp添加noexcept修饰的移动赋值运算符,erase操作会优先调用移动语义转移元素值,你可以在移动逻辑中处理原对象的清理:
temp& operator=(temp&& obj) noexcept { // 先输出原对象信息,相当于感知被擦除元素的生命周期结束 std::cout << "\n**Clean erased element**"; printInfo(); // 转移新值 objType = obj.objType; obj.objType = TYPE_DEFAULT; return *this; }
添加后erase移动元素时,会先打印原[0,2)区间元素的清理日志,符合你要感知被擦除元素生命周期结束的需求。
方案3:手动构建新vector替换原对象
如果不想修改类也不想换容器,可以提取要保留的元素构建新vector,替换原有对象:
// 替换原来的vec.erase调用 std::vector<temp> new_vec; new_vec.reserve(vec.size() - (end - start)); // 移动保留的元素到新容器 std::move(vec.begin() + end, vec.end(), std::back_inserter(new_vec)); vec.swap(new_vec);
该方式下原有vector的所有元素(包括要擦除的[0,2)区间)会在new_vec销毁时触发析构,保留的元素已经被移动到新的vec中不受影响。
内容的提问来源于stack exchange,提问作者Vishal Sharma
相关产品推荐
相关产品推荐

