オブジェクト指向プログラミングにおいて、オブジェクトのコピーとは、オブジェクト指向プログラミングにおけるデータ単位である既存のオブジェクトのコピーを作成することです。結果として得られるオブジェクトは、オブジェクトのコピー、または単に元のオブジェクトのコピーと呼ばれます。コピーは基本的な処理ですが、微妙な点があり、かなりのオーバーヘッドが発生する場合があります。オブジェクトをコピーする方法はいくつかありますが、最も一般的なのはコピーコンストラクタまたはクローニングです。コピーは主に、コピーを変更または移動したり、現在の値を保持したりするために行われます。これらのいずれも必要ない場合は、コピーが発生しないため、元のデータへの参照で十分かつ効率的です。
一般的に、オブジェクトは複合データを格納します。単純なケースでは、初期化されていない新しいオブジェクトを割り当て、元のオブジェクトからすべてのフィールド(属性)をコピーすることでコピーできますが、より複雑なケースでは、この方法では望ましい動作が得られません。
ほとんどのオブジェクトの設計目標は、実際にはそうではないにもかかわらず、あたかも一枚の巨大なブロックから作られているかのような外観を与えることです。オブジェクトは複数の異なる部品で構成されているため、コピーは容易ではありません。この問題を解決するために、いくつかの戦略が存在します。
フィールド x iを含むオブジェクト A を考えます(より具体的には、A が文字列で、x iがその文字の配列である場合を考えます)。A のコピーを作成するためのさまざまな戦略があり、これらはシャローコピーとディープコピーと呼ばれます。多くの言語では、1 つの戦略または両方の戦略による汎用的なコピーが許可されており、1 つのコピー操作または別々のシャローコピーとディープコピーの操作が定義されています。[ 1 ]さらに浅いのは、既存のオブジェクト A への参照を使用することです。この場合、新しいオブジェクトは作成されず、新しい参照のみが作成されます。
浅いコピーと深いコピーという用語は、Smalltalk -80に由来します。[ 2 ]オブジェクトの等価性を比較する場合も同様の区別があります。最も基本的な違いは、同一性 (同じオブジェクト) と等価性 (同じ値) の違いであり、これは 2 つのオブジェクト参照の浅い等価性と (1 レベルの) 深い等価性に対応しますが、さらに、等価性とは問題のオブジェクトのフィールドのみを比較することなのか、それとも一部またはすべてのフィールドを逆参照してその値を順番に比較することなのか (たとえば、2 つのリンク リストは、同じノードを持っている場合、または同じ値を持っている場合に等しいのでしょうか?) という問題があります。



オブジェクトをコピーする方法の 1 つは、シャロー コピーです。この場合、新しいオブジェクト B が作成され、A のフィールドの値が B にコピーされます。[ 3 ] [ 4 ] [ 5 ]これは、フィールドごとのコピー、[ 6 ] [ 7 ] [ 8 ]フィールド フォー フィールド コピー、またはフィールド コピーとも呼ばれます。[ 9 ]フィールドの値がオブジェクトへの参照 (たとえば、メモリ アドレス) である場合、参照がコピーされるため、A と同じオブジェクトを参照することになります。フィールドの値がプリミティブ型である場合、プリミティブ型の値がコピーされます。プリミティブ型を持たない言語 (すべてがオブジェクトである言語) では、コピー B のすべてのフィールドは、元の A のフィールドと同じオブジェクトへの参照になります。したがって、参照されるオブジェクトは共有されるため、これらのオブジェクトのいずれか (A または B) が変更されると、その変更はもう一方のオブジェクトにも反映されます。シャロー コピーは、ビットを正確にコピーするだけで実装できるため、通常はシンプルでコストも低くなります。
別の方法として、ディープコピーという方法があります。これは、フィールドの参照を解除することを意味します。つまり、オブジェクトへの参照をコピーするのではなく、参照されているオブジェクトの新しいコピーを作成し、それらへの参照をBに配置します。コンテンツは共有されないため、後からコンテンツに変更を加えても、AまたはBのいずれかに固有のものとなります。
より複雑なケースでは、コピー内の一部のフィールドは元のオブジェクトと共有値を持つ必要があり(浅いコピーの場合)、これは「関連付け」関係に対応します。一部のフィールドはコピーを持つ必要があり(深いコピーの場合)、これは「集約」関係に対応します。これらのケースでは、一般的にコピーのカスタム実装が必要です。この問題と解決策は Smalltalk-80 に遡ります。[ 10 ]あるいは、フィールドを浅いコピーまたは深いコピーを必要とするものとしてマークし、コピー操作を自動的に生成することもできます(比較操作についても同様です)。[ 1 ]ただし、これはほとんどのオブジェクト指向言語では実装されていませんが、Eiffel では部分的にサポートされています。[ 1 ]
オブジェクト指向プログラミング言語のほぼすべてに、オブジェクトをコピーする何らかの方法が備わっています。しかし、ほとんどの言語はプログラムに必要なオブジェクトをほとんど提供していないため、プログラマーはオブジェクトをどのようにコピーするかを定義する必要があります。そもそも、2つのオブジェクトが同一であるか、あるいは比較可能であるかを定義する必要があるのと同様です。多くの言語には、デフォルトの動作が用意されています。
コピーの解決方法は言語によって異なり、また、その言語がどのようなオブジェクト概念を持っているかによっても異なります。
遅延コピーは、ディープコピーの実装方法です。オブジェクトを最初にコピーする際には、(高速な)シャローコピーが使用されます。また、データを共有するオブジェクトの数を追跡するためにカウンタも使用されます。プログラムがオブジェクトを変更したい場合、カウンタを調べることでデータが共有されているかどうかを判断し、必要に応じてディープコピーを実行できます。
遅延コピーは、ディープコピーと同様に外部を参照しますが、可能な限りシャローコピーの速度を利用します。欠点は、カウンターのために基本コストがかなり高いものの一定であることです。また、状況によっては循環参照が問題を引き起こす可能性があります。
遅延コピーはコピーオンライトに関連しています。
C++では、コピーコンストラクタを呼び出すと、デフォルトで遅延コピーが発生します。
以下では、最も広く使われているオブジェクト指向言語の一つであるJavaの例を示します。これにより、オブジェクト指向言語がこの問題に対処するほぼすべての方法を網羅できるはずです。
C++とは異なり、Javaのオブジェクトは常に参照を介して間接的にアクセスされます。オブジェクトは暗黙的に作成されることはなく、常に参照変数によって渡されるか割り当てられます。(Javaのメソッドは常に値渡しですが、渡されるのは参照変数の値です。)[ 11 ] Java仮想マシンはガベージコレクションを管理し、オブジェクトがアクセスできなくなった後にクリーンアップします。Javaでは、任意のオブジェクトを自動的にコピーする方法はありません。
コピーは通常、クラスのclone() メソッドによって実行されます。このメソッドは通常、親クラスの clone() メソッドを呼び出してコピーを取得し、その後、カスタムのコピー処理を実行します。最終的に、これはObject(最上位のクラス) の clone() メソッドに到達し、オブジェクトと同じクラスの新しいインスタンスを作成し、すべてのフィールドを新しいインスタンスにコピーします (「シャローコピー」)。このメソッドを使用する場合、クラスはCloneableマーカーインターフェイスを実装する必要があります。そうでない場合は、 「Clone Not Supported Exception」がスローされます。親クラスからコピーを取得した後、クラス独自の clone() メソッドは、ディープコピー (オブジェクトが参照する構造の一部を複製する) や新しいインスタンスに新しい一意の ID を割り当てるなど、カスタムのクローン機能を提供することができます。
clone() の戻り値の型は ですが、Java の共変戻り値型Objectのサポートにより、クローン メソッドの実装者は代わりにクローンされるオブジェクトの型を記述することもできます。 clone() を使用する利点の 1 つは、オーバーライド可能なメソッドであるため、任意のオブジェクトに対して clone() を呼び出すことができ、呼び出し元のコードがそのクラスを知る必要がない (コピー コンストラクタでは必要となる) ことです。
欠点としては、抽象型に対して clone() メソッドにアクセスできない場合が多いことが挙げられます。Java のほとんどのインターフェースや抽象クラスは、パブリックな clone() メソッドを規定していません。そのため、多くの場合、clone() メソッドを使用する唯一の方法は、オブジェクトのクラスがわかっている場合に限られますが、これは可能な限り汎用的な型を使用するという抽象化の原則に反します。たとえば、Java で List の参照を持っている場合、List にはパブリックな clone() メソッドが規定されていないため、その参照に対して clone() を呼び出すことはできません。ArrayList やLinkedListなどの List の実装は一般的に clone() メソッドを備えていますが、オブジェクトのクラス型を持ち歩くのは不便で、抽象化の観点からも好ましくありません。
Javaでオブジェクトをコピーするもう1つの方法は、インターフェースを介してシリアライズするSerializableことです。これは通常、永続化やワイヤプロトコルの目的で使用されますが、オブジェクトのコピーを作成します。また、クローンとは異なり、オブジェクトの循環グラフを適切に処理するディープコピーを、プログラマーの最小限の労力で簡単に利用できます。
これらの方法はいずれも、クローンやシリアライゼーションでコピーされたオブジェクトにはコンストラクタが使用されないという顕著な問題を抱えています。これは、初期化が不適切なデータによるバグを引き起こしたり、finalメンバフィールドの使用を妨げたり、メンテナンスを困難にしたりする可能性があります。一部のユーティリティは、ディープクローニングライブラリのように、リフレクションを使用してオブジェクトをディープコピーすることで、これらの問題を克服しようとしています。[ 12 ]
Eiffelのランタイム オブジェクトには、参照を介して間接的にアクセスするか、フィールドがそれらを使用するオブジェクト内に埋め込まれた拡張オブジェクトとしてアクセスできます。つまり、オブジェクトのフィールドは外部または内部に格納されます。
Eiffel クラスANYには、オブジェクトの浅いコピーと深いコピー、およびクローンを作成するための機能が含まれています。すべての Eiffel クラスは を継承しているANYため、これらの機能はすべてのクラスで利用可能であり、参照オブジェクトと展開オブジェクトの両方に適用できます。
このcopy機能は、あるオブジェクトから別のオブジェクトへ、フィールド単位で浅いコピーを実行します。この場合、新しいオブジェクトは作成されません。yが にコピーされた場合、の適用前にxが参照していたオブジェクトは、機能の適用完了後もによって参照されます。ycopyxcopy
の浅い複製である新しいオブジェクトを作成するにはy、この機能twinを使用します。この場合、ソースのフィールドと同一のフィールドを持つ新しいオブジェクトが1つ作成されます。
この機能は、必要に応じての子孫で再定義できるtwin機能に依存しています。の結果はアンカー型です。copyANYtwinlike Current
ディープコピーとディープツインの作成は、クラスから継承された機能とを使用して実行できますdeep_copy。これらの機能は、オブジェクト構造全体内のすべてのオブジェクトを複製するため、多数の新しいオブジェクトを作成する可能性があります。既存のオブジェクトへの参照を単にコピーするのではなく、新しい複製オブジェクトが作成されるため、ディープ操作は、シャロー操作よりもパフォーマンスの問題の原因になりやすくなります。deep_twinANY
C#では、インターフェースを使用する代わりにICloneable、ジェネリック拡張メソッドを使用してリフレクションによるディープコピーを作成できます。これには 2 つの利点があります。まず、コピーするプロパティや変数を個別に指定することなく、すべてのオブジェクトをコピーできる柔軟性があります。次に、型がジェネリックであるため、コンパイラがコピー先のオブジェクトとコピー元のオブジェクトの型が同じであることを保証します。
Objective-Cでは、メソッドcopyとメソッドmutableCopyはすべてのオブジェクトに継承され、コピーを実行するために使用されます。後者は、元のオブジェクトの可変型を作成するためのものです。これらのメソッドは、それぞれメソッドcopyWithZoneとmutableCopyWithZoneメソッドを呼び出してコピーを実行します。オブジェクトがcopyWithZoneコピー可能になるには、対応するメソッドを実装する必要があります。
OCamlでは、ライブラリ関数Oo.copyはオブジェクトの浅いコピーを実行します。
Pythonでは、ライブラリのコピー モジュールは、それぞれ関数と関数を介してオブジェクトのシャロー コピーとディープ コピーを提供しますcopy()。deepcopy()[ 13 ]プログラマーは、カスタムのコピー実装を提供するために、オブジェクトに特別なメソッド__copy__()とを定義することができます。__deepcopy__()
Rubyでは、すべてのオブジェクトはシャローコピーを実行するための2つのメソッド、cloneとdupを継承しています。この2つのメソッドの違いは、clonecloneがオブジェクトの汚染状態、凍結状態、および存在する可能性のあるシングルトンdupメソッドをコピーするのに対し、 dupは汚染状態のみをコピーする点です。ディープコピーは、オブジェクトのバイトストリームまたはYAMLシリアライゼーションをダンプおよびロードすることで実現できます。あるいは、deep_dive gem を使用して、オブジェクトグラフの制御されたディープコピーを実行することもできます。
Rustでは、構造体はトレイトcloneを使用してメソッドを実装できますClone。[ 14 ] [ 15 ]
Perlでは、ネストされた構造は参照を使用して格納されるため、開発者は構造全体をループしてデータを再参照するか、Storabledclone()モジュールの関数を使用できます。
VBAでは、特定の型の変数への代入はObjectシャローコピーであり、その他のすべての型 (数値型、文字列型、ユーザー定義型、配列) への代入はディープコピーです。したがって、Set代入のキーワードはシャローコピーを、(オプションの) キーワードはLetディープコピーを示します。VBA には、オブジェクトのディープコピーを行うための組み込みメソッドはありません。
shallowCopy)。値がコピーされる場合は共有されません(deepCopy)。」copy is shallowCopy. In subclasses in which copying must result in a special combination of shared and unshared variables, the method associated with copy is usually reimplemented, rather than the method associated with shallowCopy or deepCopy."