解構函式
解構函式是一種特殊的成員函式,它在物件的生存期結束時被呼叫。解構函式的目的是釋放物件在其生存期內可能獲取的資源。
解構函式不能是協程。 |
(C++20 起) |
目錄 |
[編輯] 語法
解構函式(C++20 前)準解構函式(C++20 起)使用以下形式的成員函式宣告符宣告
帶波浪號的類名 ( 形參列表 (可選) ) 異常說明 (可選) 屬性 (可選) |
|||||||||
帶波浪號的類名 | - | 一個識別符號表示式,後面可以跟隨一個屬性列表,並且(C++11 起)可以被一對括號括起來 | ||||||
形參列表 | - | 形參列表 (必須為空或 void) | ||||||
異常規範 | - |
| ||||||
屬性 | - | (C++11 起) 屬性列表 |
一個準(C++20 起)解構函式宣告的宣告說明符中允許的說明符只有constexpr
、(C++11 起) friend
、inline
和 virtual
(特別地,不允許有返回型別)。
帶波浪號的類名的識別符號表示式必須具有以下形式之一
- 否則,識別符號表示式是一個限定識別符號,其末端的非限定識別符號為 ~ 後跟由該限定識別符號的非末端部分所指名的類的注入類名。
[編輯] 解釋
每當物件的生存期結束時,就會隱式呼叫解構函式,這包括:
|
(C++11 起) |
- 對於具有自動儲存期的物件以及其生命週期因繫結到引用而被延長的臨時物件,在作用域結束時
- 對於具有動態儲存期的物件,在使用 delete 表示式時
- 對於無名臨時物件,在完整表示式結束時
- 當異常逃離其塊且未被捕獲時,對於具有自動儲存期的物件,在棧回溯期間
解構函式也可以被顯式呼叫。
準解構函式一個類可以有一個或多個準解構函式,其中一個被選為該類的解構函式。 為了確定哪個準解構函式是解構函式,在類定義結束時,會對類中宣告的具有空引數列表的準解構函式執行過載決議。如果過載決議失敗,則程式非良構。解構函式的選擇不會ODR 式使用所選的解構函式,且所選的解構函式可以是被刪除的。 所有準解構函式都是特殊成員函式。如果類 執行此程式碼 #include <cstdio> #include <type_traits> template<typename T> struct A { ~A() requires std::is_integral_v<T> { std::puts("~A, T is integral"); } ~A() requires std::is_pointer_v<T> { std::puts("~A, T is a pointer"); } ~A() { std::puts("~A, T is anything else"); } }; int main() { A<int> a; A<int*> b; A<float> c; } 輸出 ~A, T is anything else ~A, T is a pointer ~A, T is integral |
(C++20 起) |
[編輯] 可能被呼叫的解構函式
類 T
的解構函式在以下情況下是可能被呼叫的
- 它被顯式或隱式呼叫。
- 一個 new 表示式建立了型別為
T
的物件陣列。 - return 語句的結果物件型別為
T
。 - 一個數組正在進行聚合初始化,並且其元素型別是
T
。 - 一個類物件正在進行聚合初始化,並且它有一個型別為
T
的成員,其中T
不是匿名聯合體型別。 - 一個可能被構造的子物件在非委託(C++11 起)建構函式中型別為
T
。 - 構造了一個型別為
T
的異常物件。
如果一個可能被呼叫的解構函式是被刪除的或(C++11 起)從呼叫上下文中不可訪問的,則程式非良構。
[編輯] 隱式宣告的解構函式
如果一個類型別沒有提供使用者宣告的準(C++20 起)解構函式,編譯器將總是宣告一個解構函式作為其類的 inline public 成員。
與任何隱式宣告的特殊成員函式一樣,隱式宣告的解構函式的異常說明是非丟擲的,除非任何可能被構造的基類或成員的解構函式是可能丟擲的(C++17 起)隱式定義會直接呼叫一個具有不同異常說明的函式(C++17 前)。實際上,隱式解構函式是 noexcept 的,除非該類被一個基類或成員“毒化”,而該基類或成員的解構函式是 noexcept(false)。
[編輯] 隱式定義的解構函式
如果一個隱式宣告的解構函式沒有被刪除,那麼當它被 ODR 式使用時,編譯器會隱式定義它(即,生成並編譯一個函式體)。這個隱式定義的解構函式有一個空的函式體。
如果這滿足了 constexpr 解構函式(C++23 前) constexpr 函式(C++23 起)的要求,那麼生成的解構函式是 constexpr 的。 |
(C++20 起) |
被刪除的解構函式類
|
(C++11 起) |
[編輯] 平凡解構函式
類 T
的解構函式是平凡的,如果滿足以下所有條件:
- 解構函式是隱式宣告的(C++11 前)非使用者提供的(C++11 起)。
- 解構函式不是虛擬函式。
- 所有直接基類都有平凡解構函式。
|
(直到 C++26) |
|
(C++26 起) |
平凡解構函式是一個不執行任何操作的解構函式。具有平凡解構函式的物件不需要 delete 表示式,可以透過簡單地釋放其儲存來處理。所有與 C 語言相容的資料型別(POD 型別)都是可平凡銷燬的。
[編輯] 銷燬順序
對於使用者定義的或隱式定義的解構函式,在執行完解構函式體並銷燬了函式體內分配的任何自動物件之後,編譯器會以宣告的相反順序呼叫類的所有非靜態非變體資料成員的解構函式,然後以構造的相反順序呼叫所有直接非虛基類的解構函式(這些解構函式又會呼叫它們的成員和基類的解構函式等),然後,如果這個物件是最終派生類,它會呼叫所有虛基類的解構函式。
即使直接呼叫解構函式(例如 obj.~Foo();),~Foo() 中的 return 語句也不會立即將控制權返回給呼叫者:它會先呼叫所有那些成員和基類的解構函式。
[編輯] 虛解構函式
透過指向基類的指標刪除物件會引發未定義行為,除非基類中的解構函式是 virtual 的
class Base { public: virtual ~Base() {} }; class Derived : public Base {}; Base* b = new Derived; delete b; // safe
一個常見的指導原則是,基類的解構函式必須是公有且虛的,或者是受保護且非虛的。
[編輯] 純虛解構函式
一個準(C++20 起)解構函式可以被宣告為純虛的,例如在一個需要被設為抽象但沒有其他合適的函式可以宣告為純虛的基類中。純虛解構函式必須有定義,因為當派生類被銷燬時,所有基類的解構函式總是會被呼叫。
class AbstractBase { public: virtual ~AbstractBase() = 0; }; AbstractBase::~AbstractBase() {} class Derived : public AbstractBase {}; // AbstractBase obj; // compiler error Derived obj; // OK
[編輯] 異常
與任何其他函式一樣,解構函式可以透過丟擲異常來終止(這通常要求它被顯式宣告為 noexcept(false))(C++11 起),但是,如果這個解構函式恰好在棧回溯期間被呼叫,那麼會呼叫 std::terminate 代替。
雖然 std::uncaught_exceptions 有時可用於檢測正在進行的棧回溯,但通常認為允許任何解構函式透過丟擲異常來終止是一種不好的做法。儘管如此,一些庫還是使用了這個功能,例如 SOCI 和 Galera 3,它們依賴於無名臨時物件的解構函式在構造該臨時物件的完整表示式結束時丟擲異常的能力。
庫基礎 TS v3 中的 std::experimental::scope_success 可能有一個可能丟擲的解構函式,它在作用域正常退出且退出函式丟擲異常時丟擲異常。
[編輯] 注意
對普通物件(如區域性變數)直接呼叫解構函式,當在作用域結束時再次呼叫解構函式時,會引發未定義行為。
在泛型上下文中,解構函式呼叫語法可以用於非類型別的物件;這被稱為偽解構函式呼叫:見成員訪問運算子。
功能測試宏 | 值 | 標準 | 特性 |
---|---|---|---|
__cpp_trivial_union |
202502L |
(C++26) | 放寬聯合體的特殊成員函式的平凡性要求 |
[編輯] 示例
#include <iostream> struct A { int i; A(int num) : i(num) { std::cout << "ctor a" << i << '\n'; } (~A)() // but usually ~A() { std::cout << "dtor a" << i << '\n'; } }; A a0(0); int main() { A a1(1); A* p; { // nested scope A a2(2); p = new A(3); } // a2 out of scope delete p; // calls the destructor of a3 }
輸出
ctor a0 ctor a1 ctor a2 ctor a3 dtor a2 dtor a3 dtor a1 dtor a0
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 193 | C++98 | 解構函式中的自動物件是在 類的基類和成員子物件銷燬之前還是之後 銷燬是未指定的 |
它們在銷燬 那些子物件之前 被銷燬 |
CWG 344 | C++98 | 解構函式的宣告符語法存在缺陷(與 CWG 問題 194 和 CWG 問題 263 有相同的問題) |
將語法更改為專門的 函式宣告符語法 |
CWG 1241 | C++98 | 靜態成員可能在解構函式 執行後立即被銷燬 |
只銷毀非 靜態成員 |
CWG 1353 | C++98 | 隱式宣告的解構函式被定義為已刪除的 條件沒有考慮多維陣列型別 |
考慮這些型別 |
CWG 1435 | C++98 | “類名”在解構函式的 宣告符語法中的含義不明確 |
將語法更改為專門的 函式宣告符語法 |
CWG 2180 | C++98 | 非最終派生類的解構函式 會呼叫其虛直接基類的解構函式 |
它將不會呼叫那些解構函式 |
CWG 2807 | C++20 | 宣告說明符可以包含 consteval | 已禁止 |