【c++】容器扩容导致的类实例资源被错误释放

发布于:2025-07-09 ⋅ 阅读:(17) ⋅ 点赞:(0)

BUG记录

表现为新实例被存入前,容器内部的旧实例的析构被意外调用

因为 std::vector 在容量不足时,会自动扩容,把旧元素「搬」到新内存,然后析构旧内存上的那些对象。然后由于LKMotorController 类里没有正确处理移动语义(或复制语义),旧实例被析构时就会释放它持有的资源(比如关闭 CAN 句柄、销毁线程、解绑回调……),从而导致后续真正正在运行的控制器也被意外破坏。

解决措施

最简单的方案

预留足够的容器空间
如果在执行前就知道会有几个实例,就可以用这种方式

std::vector<obj_class> objs;
objs.reserve(objs_size);

最通用的

为类实现移动语义

移动语义需要遵循Rule of Five

如果你定义或禁用了析构函数、拷贝构造、拷贝赋值、移动构造、移动赋值 中的任何一个,通常需要显式声明其余四个。

  • 移动构造函数
    • 形式
      • 类名(类名&& 即将被销毁的对象)
      • 要求函数名和类名相同,
      • 且该函数的首个参数类型必须和类本身相同,
      • 且参数本身必须是即将被销毁的对象,也就是“右值对象”
        example_class(example_class&&  trush_obj) noexcept 
        {
        	//将trush_obj中需要的属性和实例都转移到当前实例中
        	//然后将trush_obj中被“窃取”的属性全都指向null,以防旧对象析构时误释放
        }
        
    • 特性
      • 类似于重写,如果正常初始化,则类会调用普通的构造函数;如果使用临时对象初始化,则会调用移动构造函数
      • 如果既没有定义拷贝构造函数,也没有定义移动构造函数,则会使用默认构造函数
      • 调用默认构造函数后,如果临时对象的属性有移动构造函数,那就用它的移动构造,如果没有,就用拷贝构造
  • 移动赋值运算符
    • 形式
      和移动构造函数基本没啥区别
      example_class& operator=(example_class&&  trush_obj) noexcept 
      {
      	//将trush_obj中需要的属性和实例都转移到当前实例中
      	//然后将trush_obj中被“窃取”的属性全都指向null,以防旧对象析构时误释放
      }
      
  • 普通析构函数和以前一样
  • 由于不需要拷贝操作,为了防止误用,需要显式删除
    example_class(const example_class&) = delete;
    example_class& operator=(const example_class&) = delete;
    
  • 这五部分均显式定义完后,该类就算完整拥有的移动语义,当然涉及移动资源释放时,仍需要手动管理逻辑

网站公告

今日签到

点亮在社区的每一天
去签到