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;
- 这五部分均显式定义完后,该类就算完整拥有的移动语义,当然涉及移动资源释放时,仍需要手动管理逻辑