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;
- 這五部分均顯式定義完后,該類就算完整擁有的移動語義,當然涉及移動資源釋放時,仍需要手動管理邏輯