首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

C++ 臨時物件導致解構函式被呼叫兩次的問題分析與解決

     編輯:關於VC++

在 C++ 中,臨時物件的生命週期管理若不當,會導致解構函式被重複呼叫,造成重複釋放記憶體、程式崩潰等嚴重錯誤。本文透過一個 stack 類別範例,說明臨時物件產生、複製建構、解構時機,並提供三種解決方案。

問題重現程式碼

#include <cstring>
#include <iostream>
using namespace std;

class Stack {
public:
  Stack(const char* value);
  ~Stack();
  char* data;
};

Stack::Stack(const char* value) {
  if (value) {
    data = new char[strlen(value) + 1];
    strcpy(data, value);
  } else {
    data = new char[1];
    *data = '\0';
  }
  cout << "Construct: " << data << endl;
}

Stack::~Stack() {
  cout << "Destruct: " << data << endl;
  delete[] data;
}

// 測試函式:傳值回傳會產生臨時物件
Stack GetStack() {
  return Stack("Temp"); // 回傳臨時物件
}

int main() {
  Stack s = GetStack(); // 複製建構 (或移動建構)
  cout << "Main end" << endl;
  return 0;
}

輸出結果 (未優化編譯)

Construct: Temp
Destruct: Temp      // 臨時物件解構
Destruct: Temp      // s 解構 → 重複 delete[] → Crash!

根因分析

  1. GetStack() 回傳 Stack("Temp") 產生臨時物件 (rvalue)。
  2. 呼叫端 Stack s = GetStack() 以複製建構函式 初始化 s (C++17 前可能經歷兩次複製,C++17 保證複製省略 RVO)。
  3. 若類別未定義複製建構函式,編譯器產生預設淺層複製,導致 s.data 與臨時物件 data 指向同一記憶體。
  4. 臨時物件於完整運算式結束時解構,釋放記憶體;隨後 s 解構再次 delete[] 同一指標 → Double Free / Heap Corruption。

三種解決方案

1. 實作深層複製建構函式與指派運算子 (Rule of Three)

Stack(const Stack& other) {
  data = new char[strlen(other.data) + 1];
  strcpy(data, other.data);
}
Stack& operator=(const Stack& other) {
  if (this != &other) {
    delete[] data;
    data = new char[strlen(other.data) + 1];
    strcpy(data, other.data);
  }
  return *this;
}

2. 使用智慧指標 (C++11 std::unique_ptr / std::shared_ptr)

#include <memory>
class Stack {
  std::unique_ptr data;
public:
  Stack(const char* v) { /* 配置 unique_ptr */ }
  // 無需手寫解構/複製/移動,unique_ptr 自動處理
};

3. 利用移動語意 (C++11) 避免複製

Stack(Stack&& other) noexcept : data(other.data) { other.data = nullptr; }
Stack& operator=(Stack&& other) noexcept {
  if (this != &other) { delete[] data; data = other.data; other.data = nullptr; }
  return *this;
}

配合 return Stack("Temp"),編譯器優先使用移動建構,零開銷轉移擁有權。

最佳實務總結

  • 管理動態資源的類別必須遵循 Rule of Three (C++98) 或 Rule of Five (C++11)。
  • 優先使用 std::string、std::vector、智慧指標等標準庫容器,避免手動 new/delete。
  • 啟用編譯器警告 (/Wall、-Wextra) 捕捉缺失複製建構函式的類別。
  • C++17 起強制複製省略 (RVO/NRVO),但仍需正確實作移動/複製語意以確保語意正確與可攜性。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved