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

VC++2005 中返回局部對象時析構函數被多次調用的原因分析

     編輯:關於C++

現象重現

在 VC++2005 中編譯執行以下代碼:

#include <iostream>
using namespace std;

struct A {
    A() { cout << "A()" << endl; }
    // A(const A&) { cout << "A(A)" << endl; }
    A& operator=(const A&) { cout << "A=A" << endl; }
    ~A() { cout << "~A()" << endl; }
};

A foo(A a) {
    cout << "---" << endl;
    return a;
}

int main() {
    {
        foo(A());
    }
    system("pause");
    return 0;
}

輸出結果為:

A()
---
~A()
~A()
~A()

問題在於:return a; 為什麼會觸發兩次析構函數調用?

簡化測試

將函數返回類型改為 void,觀察行為差異:

void foo(A a) { }

int main() {
    {
        foo(A());
    }
    system("pause");
    return 0;
}

情況一:未聲明拷貝構造函數

輸出:

A()
~A()
~A()

情況二:聲明了拷貝構造函數

取消註釋 A(const A&) { cout << "A(A)" << endl; }

輸出:


A()
~A()

深度解析

臨時對象的生命週期

A() 創建一個臨時對象,作為實參傳遞給 foo(A a)。由於參數是按值傳遞,編譯器需要構造參數 a。

無拷貝構造函數時(編譯器合成)

  • 編譯器合成一個「逐位複製」的拷貝構造函數。
  • 調用 foo(A()) 時:先構造臨時對象(輸出 A()),再用合成的拷貝構造函數構造參數 a(無輸出)。
  • 函數體結束時,銷毀參數 a(第一次 ~A())。
  • 隨後銷毀臨時對象(第二次 ~A())。
  • 若函數有返回值 return a;,還會構造返回值臨時對象(再次拷貝),並在 main 中銷毀它(第三次 ~A())。

有用戶定義拷貝構造函數時

  • 編譯器不再合成拷貝構造函數,而是調用用戶提供的版本(輸出 A(A))。
  • 此時觸發了「返回值優化」(RVO/NRVO),編譯器直接在調用者的棧幀上構造對象,省去了多次拷貝與銷毀。
  • 因此只有一次構造(A())和一次析構(~A())。

結論

VC++2005 的優化策略受「是否存在用戶定義拷貝構造函數」影響:

  • 無用戶定義拷貝構造函數 → 編譯器合成逐位拷貝 → 無法應用 RVO → 產生多個臨時對象 → 多次析構。
  • 有用戶定義拷貝構造函數 → 編譯器啟用 RVO → 直接構造於目標位置 → 單次析構。

這屬於編譯器實現細節,現代 C++(C++17 起)強制要求複製省略,行為更加一致。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved