オブジェクト指向プログラミングでは、デストラクタ(dtor [ 1 ]と略されることもある)は、オブジェクトのメモリが解放される直前に機械的に呼び出されるメソッドです。 [ 2 ]これは、オブジェクトのライフタイムがスコープにバインドされ、実行がスコープを抜けるとき、ライフタイムが終了する別のオブジェクトに埋め込まれているとき、または動的に割り当てられ、明示的に解放されるときに発生します。その主な目的は、オブジェクトがライフタイム中に取得したリソース(メモリ割り当て、開いているファイルまたはソケット、データベース接続、リソースロックなど)を解放し、またはオブジェクトへの参照を保持している可能性のある他のエンティティから登録を解除することです。デストラクタは、リソース取得初期化(RAII)で必要です。
ほとんどの自動ガベージコレクションアルゴリズムでは、メモリの解放はオブジェクトがアクセス不能になってからかなり時間が経ってから行われるため、デストラクタは時間制約のある用途には適していません。これらの言語では、リソースの解放は、レキシカル構造(try-finally、Pythonの`try` with、Javaの`try-with-resources`など)または関数の明示的な呼び出し(明示的な削除と同等)によって行われます。特に、多くのオブジェクト指向言語では` dispose`パターンが使用されています。
Xコンストラクタを持つクラスにはX()デストラクタがあります~X())。[ 2 ]~X())。歴史的にはデストラクタと呼ばれていましたが、混乱を避けるため現在はファイナライザと呼ばれています。 [ 3 ]~this()(コンストラクタはとして宣言されますthis())。close()2つのインターフェースjava.lang.Closeable(非推奨)とによって提供されるメソッドがありますjava.lang.AutoCloseable。Java 9以降では、「デストラクタ」はに置き換えられていますjava.lang.ref.Cleaner。Javaには以前はもありましたがObject.finalize()、これも非推奨になりました。destructor、任意の名前にすることができますが、慣例としては ですDestroy。dealloc。DESTROY。Mooseオブジェクト システム拡張機能では、 という名前ですDEMOLISH。__destruct。以前のバージョンのPHPにはデストラクタはありませんでした。[ 4 ]__del__。Python 2 ではデストラクタと呼ばれていましたが、[ 5 ] Python 3 ではファイナライザと呼ばれています。[ 6 ]drop、トレイトによって提供されますstd::ops::Drop。[ 7 ]deinit。このキーワードはJavaScriptとTypeScriptdeleteにも存在しますが、C++とは異なり、これらの言語ではクラスからプロパティを削除します。delete
C言語にはオブジェクトがないため、コンストラクタもデストラクタもありません。しかし、割り当てと破棄を行う関数を使用して、コンストラクタとmalloc()デストラクタの手動呼び出しを抽象化することで、それらをエミュレートできますfree()。
#include <stdio.h> #include <stdlib.h>typedef struct { void * data ; // 何らかのリソース。何でも構いません。size_t size ; // リソースのサイズ} Resource ;// コンストラクタResource * createResource ( size_t size ) { Resource * res = ( Resource * ) malloc ( sizeof ( Resource )); if ( ! res ) { fprintf ( stderr , "リソースのメモリ割り当てに失敗しました。\n " ); return NULL ; } res -> data = malloc ( size ); if ( ! res -> data ) { fprintf ( stderr , "リソースデータのメモリ割り当てに失敗しました。\n " ); free ( res ); return NULL ; } res -> size = size ; return res ; }// デストラクタvoid destroyResource ( Resource * res ) { if ( res ) { if ( res -> data ) { free ( res -> data ); } free ( res ); } }int main () { // 50バイトのリソースを割り当てますResource * myResource = createResource ( 50 );if ( ! myResource ) { return 1 ; }for ( size_t i = 0 ; i < myResource -> size ; ++ i ) { (( char * ) myResource -> data )[ i ] = ( char )( i % 256 ); // ダミーデータ}printf ( "リソースデータの最初の10バイト:" ); for ( size_t i = 0 ; i < 10 ; ++ i ) { printf ( "%d" , (( char * ) myResource- > data )[ i ]); } printf ( " \n " );// リソースを解放するdestroyResource ( myResource ); }GNU Compiler CollectionのCコンパイラには、デストラクタを実装するための2つの拡張機能が付属しています。
デストラクタはクラスと同じ名前ですが、前にチルダ( ~ ) が付きます。 [ 2 ]例えば、 というクラスのFooデストラクタは となります~Foo()。デストラクタにはパラメータも戻り値の型もありません。[ 2 ]上記のように、オブジェクトのデストラクタは、オブジェクトのライフタイムが終了するたびに呼び出されます。[ 2 ]オブジェクトが自動変数として作成された場合、オブジェクトがスコープ外になるとライフタイムが終了し、デストラクタが自動的に呼び出されます。C++ にはガベージ コレクションがないため、オブジェクトがnew(動的にヒープ上に) で作成された場合、そのデストラクタは、オブジェクトへのポインタに演算子が適用されたときに呼び出されます。通常、この操作は別のデストラクタ内で発生し、通常はスマート ポインタdeleteオブジェクトのデストラクタです。
継承階層において、基底クラスに仮想デストラクタを宣言することで、基底クラスへのポインタを介してオブジェクトが削除された際に、派生クラスのデストラクタが適切に呼び出されることが保証されます。このように削除される可能性のあるオブジェクトは、仮想デストラクタを継承する必要があります。
デストラクタは例外をスローしてはいけません[ 10 ]。すべてのデストラクタは暗黙的にマークされていますnoexceptが、明示的にマークされている場合はスローすることが許可されます。クラスがスローするデストラクタを持つクラスを継承している場合、またはデストラクタがスローするメンバ変数を持っている場合、そのデストラクタは暗黙的にではありません。[ 11 ]noexcept(false)noexcept
非クラスのスカラー型には、またはテンプレート引数を使用してアクセスできる擬似デストラクタtypedef。この構造により、特定の型にデストラクタが存在するかどうかを知らなくてもコードを記述することが可能になります。
int f () { int a = 123 ; using Int = int ; a . ~ Int (); return a ; // return 123 }標準の古いバージョンでは、擬似デストラクタは効果を持たないと規定されていましたが、欠陥報告により、呼び出されたオブジェクトのライフタイムを終了するように変更されました。[ 12 ]
C++/CLIでは、デストラクタとファイナライザの両方があり、デストラクタはクラス名の前に~を付けた名前のメソッドです (C# の場合と同様)。ファイナライザはクラス名の前に!を付けた名前のメソッドです (C# の場合と同様)。~Foo()!Foo()
安全にコピーや代入ができないオブジェクトは、対応する関数を削除済みとして宣言することで、そのようなセマンティクスから除外する必要があります。この方法の詳細については、Scott Meyers氏の人気書籍『 Effective Modern C++』(項目 11:「プライベートな未定義関数よりも削除済み関数を優先する」[ 13 ])を参照してください。削除済みとしてマークされている場合はdelete、public誤って使用しても警告が表示されないようにprivate、明示的に削除済みとしてマークする必要があります。C ++26delete以降では、削除の理由を指定できるようになりました。
C++には、オブジェクトの寿命を終了させる関数std::destroy()/ std::destroy_at()/ std::destroy_n()(および範囲指定バージョンstd::ranges::destroy()/ std::ranges::destroy_at()/ std::ranges::destroy_n()) があります。しかし、クラス型以外の型に対して呼び出された場合、擬似デストラクタが呼び出されるだけで、実際には寿命は終了しません。
import std ;using std :: formatter ; using std :: format_parse_context ;class Foo { private : size_t length ; char * data ; friend struct formatter <Foo> ; public : // コンストラクタexplicit Foo ( const char s [] = "" ) : length { std :: strlen ( s ) }, data { new char [ length + 1 ]} { std :: strcpy ( data , s ) ; }Foo ( const Foo & ) = delete ( "コピー構築が無効になっています" ); Foo & operator = ( const Foo & ) = delete ( "コピー代入が無効になっています" );// デストラクタ~ Foo () { delete [] data ; } };template <> struct std :: formatter < Foo > { // Foo のフォーマッタの定義をここに記述};int main ( int argc , char * argv []) { Foo foo ( "Hello from the stack!" ); std :: println ( "{}" , foo );Foo * foo = new Foo ( "Hello from the heap!" ); std :: println ( "{}" , * foo ); delete foo ; }「リソース取得は初期化である」(RAII)イディオムとスマートポインタを使用することで、手動によるリソースのクリーンアップを抽象化できます。JavaやC#などの他の言語にはクリーンアップ用のブロックがありますが、C++にはそのようなブロックはなく、代わりにRAIIイディオムの使用が推奨されています。finallyfinally
import std ;using std :: formatter ; using std :: format_parse_context ; using std :: unique_ptr ;class Foo { private : size_t length ; unique_ptr < char [ ] > data ; friend struct formatter <Foo> ; public : // コンストラクタexplicit Foo ( const char s [] = "" ) : length { std :: strlen ( s ) }, data { std :: make_unique < char [] > ( length + 1 )} { std :: strcpy ( data.get ( ), s ) ; }Foo ( const Foo & ) = delete ( "コピー構築が無効になっています" ); Foo & operator = ( const Foo & ) = delete ( "コピー代入が無効になっています" );// デストラクタは unique_ptr ~ Foo () = defaultによって自動的に処理されます。};template <> struct std :: formatter < Foo > { // Foo のフォーマッタの定義をここに記述};int main ( int argc , char * argv []) { Foo foo ( "Hello from the stack!" ); std :: println ( "{}" , foo );unique_ptr < Foo > foo = std :: make_unique < Foo > ( "Hello from the heap!" ); std :: println ( "{}" , * foo ); }XC# のクラスのデストラクタはであり~X()、パラメータはなく、コンストラクタと同様に戻り値の型もありません。
C# には、クラスがインターフェースを実装する必要がある「dispose」パターンもありますSystem.IDisposable。C# は、Java と同様の try-with-resources ブロックをサポートしており、これは using-with-resources と呼ばれます。クラスは、System.IDisposableusing-with-resources ブロック内で使用されるために、インターフェースを実装する必要があります。
名前空間Wikipedia.Examples ;using System ; using System.Collections.Generic ; using System.Data.SqlClient ;公開レコードUser ( int Id 、string Name 、string Email );public class UserUnitOfWork : IDisposable { private readonly SqlConnection _connection ; private SqlTransaction _transaction ; private bool _disposed ;public UserUnitOfWork ( string url ) { _connection = new ( url ); _connection.Open ( ) ; _transaction = _connection.BeginTransaction ( ) ; }public List < User > GetActiveUsers () { // ... }public void Commit () { _transaction ? .Commit (); }public void Dispose () { if ( _disposed ) { return ; } try { _transaction ? .Dispose (); _connection ? .Close (); _connection ? .Dispose (); } finally { _disposed = true ; } } }class Program { static void Main ( string [] args ) { using ( UserUnitOfWork uow = new ( /* 接続文字列 */ ) { Console . WriteLine ( "UserUnitOfWork を使用しています..." ); List < User > users = uow . GetActiveUsers (); uow . Commit (); // ... } // ここで接続、トランザクションが閉じられます} }C#のデストラクタは、C++ のように手動で呼び出したり、演算子によって呼び出されたりすることはありませんdelete。ガベージコレクタによってのみ呼び出されます。

ただし、ファイナライザは、ポインタやネイティブハンドルなどの管理対象外リソースの処理など、どうしても必要な場合にのみ使用すべきです。ファイナライザを追加すると、オブジェクトがファイナライゼーションキューに移動され、少なくとも2回のガベージコレクションサイクルが必要となるため、パフォーマンスが低下し、メモリ負荷が増加するからです。
名前空間Wikipedia.Examples ;Systemを使用します。class NativeResourceHolder : IDisposable { private IntPtr _nativeHandle ; private bool _disposed ;public NativeResourceHolder () { _nativeHandle = AllocateNative (); }public void Dispose () { Dispose ( true ) ; GC.SuppressFinalize ( this ) ; }// ファイナライザー~ NativeResourceHolder () { Dispose ( false ); }protected virtual void Dispose ( bool disposing ) { if ( ! _disposed ) { // ここでマネージド リソースを解放します (存在する場合) } FreeNative ( _nativeHandle ); _disposed = true ; }private IntPtr AllocateNative () => new IntPtr ( 42 ); private void FreeNative ( IntPtr ptr ) { /* ... */ } }class Program { static void Main ( string [] args ) { NativeResourceHolder obj = new (); GC . Collect (); GC . WaitForPendingFinalizers ();Console.WriteLine ( "プログラムが終了しました" ) ; } }以下は、 D言語におけるデストラクタの使用例です。
import std.stdio ;struct Integer { int x ;this ( int value ) { x = value ; writeln ( "コンストラクタがx = " , xで呼び出されました"); }~ this () { writeln ( "x = " のデストラクタが呼び出されました" , x ); } }void main () { { Integer s = Integer ( 10 ); writeln ( "スコープ内" ); } // デストラクタはここで実行されますwriteln ( "スコープ外" ); }Java には C++ や C# に見られるようなデストラクタ ( ) があります~X()が、Java はclose()メソッドjava.lang.Closeable(非推奨) とを実装するインターフェースを提供していますjava.lang.AutoCloseable。 を実装するクラスは、 Java 7以降で利用可能な、スコープの終了時にメソッドを自動的に呼び出すjava.lang.AutoCloseable" -with-resources" ブロックで使用できます。[ 14 ]ただし、デストラクタとは異なり、メソッドはメモリの解放ではなく、リソースのクリーンアップに使用されます。メモリはガベージ コレクションによって解放されます。tryclose()clean()
パッケージorg.wikipedia.examples ;import java.sql.Connection ; import java.sql.DriverManager ; import java.sql.SQLException ; import java.util.ArrayList ; import java.util.List ;公開レコードUser ( int id , String name , String Email ) {}class UserUnitOfWork implements AutoCloseable { private final Connection conn ; private bool committed = false ;public UserUnitOfWork ( String url , String username , String password ) throws SQLException { this.connection = DriverManager.getConnection ( url , username , password ) ; this.connection.setAutoCommit ( false ) ; }public List < User > getActiveUsers () throws SQLException { // ... }public void commit ( ) throws SQLException { connection.commit ( ); committed = true ; }@Override public void close ( ) throws SQLException { try { if ( ! committed ) { connection.rollback ( ); } } finally { connection.close ( ) ; } } }public class Example { public static void main ( String [] args ) { try ( UserUnitOfWork uow = new UserUnitOfWork ( /* URL と認証情報 */ )) { System . out . println ( "UserUnitOfWork を使用しています..." ); List < User > users = uow . getActiveUsers (); uow . commit (); // ... } // try-with-resources の後、d.close() が呼び出されます} }Java 7 より前は、「try- finally」ブロックが使用され、d.close()呼び出しはそのfinallyブロック内に記述されていました。
歴史的に、Java はfinalize()のメソッドを使用していましたjava.lang.Objectが、どのスレッドが特定のオブジェクトに対してメソッドを呼び出すか、あるいはそもそも呼び出されるかどうかも保証されないため、この方法は非推奨になりました。finalize()ファイナライザは非決定論的であり (C++ のデストラクタとは異なり)、呼び出しで重要なリソースを解放すると、finalize()予測不可能な動作につながる可能性があります。[ 15 ]
Java には、 Java 仮想マシンgc()(JVM)にガベージ コレクションを実行するように指示するメソッドがあります。これは内部的には、ガベージ コレクタにガベージ コレクション サイクルをトリガーするように要求する呼び出しと同等ですjava.lang.Systemがjava.lang.Runtime、JVMはメモリを独立して管理するため、これは保証されません。は、オブジェクトがガベージ コレクションの対象であり、メソッドを持っている場合に限り、呼び出される可能性があります。Runtime.getRuntime().gc()System.gc()finalize()finalize()
パッケージorg.wikipedia.examples ;class ParentFinalizerExample { /* ... */ }class FinalizerExample extends ParentFinalizerExample { @Override protected void finalize () throws Throwable { try { System . out . println ( "finalize() が呼び出されました。クリーンアップ中です..." ); } finally { super . finalize (); // 親クラスをクリーンアップするために常に super.finalize() を呼び出す} } }public class Example { public static void main ( String [] args ) { FinalizerExample obj = new FinalizerExample (); obj = null ; System . gc (); // ガベージコレクションを要求します (保証されません) } }Java は、より安全な低レベルクリーンアップのためにクラスと もサポートしていますjava.lang.ref.Cleaner。は Java 9 で導入され、 よりも効率的で、オブジェクトが到達不能になったとき (つまり、参照が存在しなくなったとき) にクリーンアップ アクションを実行するクリーン スレッドにオブジェクトを登録することで機能します。 やと同様に、メモリを解放しません。メモリの解放はガベージ コレクションによって行われます。java.lang.ref.PhantomReferencejava.lang.ref.Cleanerjava.lang.ref.PhantomReferencejava.lang.Closeablejava.lang.AutoCloseable
パッケージorg.wikipedia.examples ;import java.lang.ref.Cleaner ; import java.lang.ref.Cleaner.Cleanable ;class Resource { private static final Cleaner cleaner = Cleaner.create ( ) ;static class State implements Runnable { private boolean cleaned = false ;@Override public void run () { cleaned = true ; System.out.println ( "Cleaner を使用してクリーンアップしました" ); } }private final State state ; private final Cleanable cleanable ;public Resource ( ) { this.state = new State ( ) ; this.cleanable = cleaner.register ( this , state ) ; }public void cleanup () { System.gc ( ); //ガベージコレクションを要求(保証されない)} }public class Example { public static void main ( String [] args ) { Resource resource = new Resource (); resource = null ; resource . cleanup (); } }java.lang.ref.PhantomReferenceJava 1.2 以降、は参照キューを使用する古いクリーンアップメカニズムです。は、java.lang.ref.PhantomReferenceオブジェクトのガベージコレクションが保留中であることを通知するためだけに使用されます。オブジェクトがガベージコレクションされると、java.lang.ref.PhantomReferenceはにエンキューされます。メモリ内にオブジェクトが存在する場合にオブジェクトにアクセスするために使用できるとはjava.lang.ref.ReferenceQueue異なり、はオブジェクトが破棄されるタイミングを検出するためにのみ使用できます。とどちらも参照カウントを増やしません。java.lang.ref.WeakReferencejava.lang.ref.PhantomReferencejava.lang.ref.PhantomReferencejava.lang.ref.WeakReference
パッケージorg.wikipedia.examples ;import java.lang.ref.PhantomReference ; import java.lang.ref.ReferenceQueue ;class Resource { private final String name ;public Resource ( String name ) { this.name = name ; }public String getName () { return name ; } }public class PhantomReferenceExample { public static void main ( String [] args ) throws InterruptedException { Resource resource = new Resource ( "My resource" ); ReferenceQueue < Resource > queue = new ReferenceQueue <> (); PhantomReference < Resource > phantomRef = new PhantomReference <> ( resource , queue );リソース= null ;System.gc ( ) ;Reference < ? extends Resource > ref = queue.poll ( ); if ( ref != null ) { System.out.printf ( "オブジェクトは収集準備完了です: %s%n" , ( ( PhantomReference <?> ) ref ) .get ( )); } } }C++ の は参照カウントをstd::weak_ptr増やさずにオブジェクトを参照するために使用されますが、は参照カウントが 0 になった後もオブジェクトにアクセスできます。一方、 C++ では、オブジェクトの参照が 0 になるとオブジェクトがすぐに破棄されるため、アクセスできません。java.lang.ref.WeakReferencestd::weak_ptr
Pythonはデストラクタをサポートしており、キーワードも持っていますが、 C++とはdel異なり、オブジェクトの参照カウントを減らすだけで、必ずしもオブジェクトを即座に破棄するわけではありません。deletedel
class Destructible : def __init__ ( self , name : str ) -> None : self . name : str = name print ( f "Created Destructible: { self . name } " )def __del__ ( self ) -> None : print ( f "デストラクタが呼び出されました: { self . name } " )if __name__ == "__main__" : d : Destructible = Destructible ( " My name" ) print ( f " Using Destructible: { d.name } " ) del dJavaやC#と同様に、Pythonにもtry-with-resourcesブロックと呼ばれるものがあり、withこれはブロックまたは「コンテキストマネージャ」と呼ばれます。ファイルやネットワーク接続などの処理に使用されます。
型からオプションをインポートclass Destructible : def __init __ ( self , name : str ) - > None : self.name : str = namedef __enter__ ( self ) -> "Destructible" : print ( f "コンテキストに入ります(リソースを割り当てます:{ self.name } )" ) return selfdef __exit__ ( self , exc_type : Optional [ type ], exc_val : Optional [ Exception ], exc_tb : Optional [ type ]) -> None : print ( f "コンテキストを終了します (リソースをクリーンアップしています: { self . name } )" )if __name__ == "__main__" : with Destructible ( "Resource A" ) as d : print ( f "コンテキスト内でリソース{ d . name }を使用しています" )# ほとんどの Python 標準ライブラリのリソースはブロックをサポートしています: with open ( file_path , "r" ) as file : print ( "ファイルの内容を読み込みます:" ) content : str = file . read () print ( content )Rustにはオブジェクト指向プログラミングの意味でのデストラクタはありませんが、スコープ外になった後に自身をクリーンアップするためのトレイトとメソッドstructを実装できます。std::ops::Dropdrop
C++のように演算子を使ってオブジェクトを明示的に破棄することはできませんが、.を使ってdelete手動で早めに呼び出すことは可能です。drop()std::mem::drop()
use std :: mem ;struct Destructible { name : String , }impl Destructible { fn new ( name : String ) -> Self { Destructible { name } } }impl Drop for Destructible { fn drop ( & mut self ) { println! ( "Dropping Destructible: {}" , self . name ); } }fn main () { { let resource_a : Destructible = Destructible :: new ( String :: from ( "Resource A" )); println! ( "Using Destructible." ); } // <--- resource_a はここでスコープ外になり、`drop()` が自動的に呼び出されますlet resource_b : Destructible = Destructible :: new ( String :: from ( "Resource B" )); println! ( "Destructible を早期に破棄します。" ); mem :: drop ( resource_b ); }ライフタイムは参照の有効性を制御するが、いつdrop()呼び出されるかを決定するものではない。
TypeScriptには手動のメモリ管理はありませんが、C#usingの-with-resourceブロックやJavaの-with-resourcesブロック、あるいはC++のリソース取得の初期化に似たリソース管理があり、ブロックを必要とせずにリソースを自動的に閉じます。TypeScriptでは、オブジェクトを自動的に閉じるには、グローバルインターフェースを実装し、メソッドを実装する必要があります。これはスコープの最後に自動的に呼び出されます。[ 16 ]tryfinallyDisposableSymbol.dispose()
import * as fs from 'fs' ;class TempFile implements Disposable { #path : string ; #handle : number ;constructor ( path : string ) { this . # path = path ; this . # handle = fs.openSync ( path , "w+" ); }write ( data : string ) : void { fs.writeSync ( this . # handle , data ) ; }[ Symbol.dispose ] ( ) : void { fs.closeSync ( this . # handle ) ; fs.unlinkSync ( this . # path ) ; } }export function doSomeWork () { using file : TempFile = new TempFile ( ".some_temp_file.txt" );if ( someCondition ()) { // ここで何か処理を行う} }Xojo (REALbasic)のデストラクタは、2 つの形式のいずれかになります。どちらの形式も、特別な名前 (パラメータも戻り値もありません) を持つ通常のメソッド宣言を使用します。古い形式では、クラスと同じ名前に ~ (チルダ) プレフィックスを付けます。新しい形式では、 という名前を使用しますDestructor。新しい形式の方が、クラスのリファクタリングが容易になるため推奨されます。
クラス・フーバー // 旧形式 サブ ~Foobar() End Sub // 新しいフォーム サブデストラクタ() End Sub エンドクラス