オブジェクト指向プログラミングにおいて、オブジェクトのコピーとは、オブジェクト指向プログラミングにおけるデータの単位である既存のオブジェクトのコピーを作成することです。作成されたオブジェクトは、オブジェクト コピーまたは単に元のオブジェクトのコピーと呼ばれます。コピーは基本的なものですが、微妙な点があり、かなりのオーバーヘッドが生じる可能性があります。オブジェクトをコピーする方法はいくつかありますが、最も一般的なのは、コピー コンストラクターまたはクローン作成です。コピーは、ほとんどの場合、コピーを変更または移動したり、現在の値を保持したりするために行われます。これらのいずれかが不要な場合は、元のデータへの参照で十分であり、コピーが行われないため、より効率的です。
一般に、オブジェクトは複合データを格納します。単純なケースでは、新しい初期化されていないオブジェクトを割り当て、元のオブジェクトからすべてのフィールド (属性)をコピーすることでコピーを実行できますが、より複雑なケースでは、これでは望ましい動作が得られません。
コピーの方法
ほとんどのオブジェクトの設計目標は、1 つのモノリシック ブロックで作られているように見えるようにすることですが、実際にはそうではありません。オブジェクトは複数の異なるパーツで構成されていると、コピーは簡単ではありません。この問題に対処するための戦略はいくつかあります。
フィールド 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 のフィールドと同じオブジェクトへの参照です。参照されたオブジェクトはこのように共有されるため、これらのオブジェクトの 1 つが変更されると (A または B から)、その変更は他のオブジェクトにも反映されます。浅いコピーは単純で、通常はコストがかかりません。これは、通常、ビットを正確にコピーするだけで実装できるためです。
ディープコピー
代替案はディープ コピーです。これは、フィールドが逆参照されることを意味します。つまり、オブジェクトへの参照がコピーされるのではなく、参照されているオブジェクトに対してオブジェクトの新しいコピーが作成され、これらへの参照が B に配置されます。コンテンツは共有されないため、どちらかのコンテンツに対する後続の変更は A または B に固有のままになります。
組み合わせ
より複雑なケースでは、コピー内の一部のフィールドは元のオブジェクトと値を共有する必要があります (浅いコピーの場合)。これは「関連」関係に対応します。また、一部のフィールドはコピーする必要があります (深いコピーの場合)。これは「集約」関係に対応します。これらのケースでは、通常、コピーのカスタム実装が必要です。この問題と解決策は Smalltalk-80 に遡ります。[10]または、フィールドを浅いコピーまたは深いコピーを必要とするものとしてマークし、コピー操作を自動的に生成することができます (比較操作の場合も同様)。 [1]ただし、これはほとんどのオブジェクト指向言語では実装されていませんが、Eiffel では部分的にサポートされています。 [ 1 ]
実装
ほぼすべてのオブジェクト指向 プログラミング言語は、オブジェクトをコピーする方法を提供しています。ほとんどの言語はプログラム用のオブジェクトをほとんど提供していないため、プログラマーは、2 つのオブジェクトが同一であるか、あるいはそもそも比較可能であるかを定義する必要があるのと同様に、オブジェクトのコピー方法を定義する必要があります。多くの言語は、何らかのデフォルトの動作を提供しています。
コピーの解決方法は言語によって異なり、オブジェクトの概念も異なります。
怠惰なコピー
遅延コピーはディープ コピーの実装です。オブジェクトを最初にコピーするときに、(高速な) 浅いコピーが使用されます。カウンターは、データを共有するオブジェクトの数を追跡するためにも使用されます。プログラムがオブジェクトを変更する場合、(カウンターを調べることによって) データが共有されているかどうかを判断でき、必要に応じてディープ コピーを実行できます。
遅延コピーは、外見上はディープ コピーと同じように見えますが、可能な場合は常にシャロー コピーの速度を活用します。欠点は、カウンターによる、かなり高いが一定の基本コストです。また、特定の状況では、循環参照が問題を引き起こす可能性があります。
遅延コピーはコピーオンライトに関連しています。
ジャワ島
以下では、最も広く使用されているオブジェクト指向言語の 1 つであるJavaの例を示します。この例は、オブジェクト指向言語がこの問題を処理できるほぼすべての方法を網羅しています。
C++とは異なり、Javaのオブジェクトは常に参照を通じて間接的にアクセスされます。オブジェクトは暗黙的に作成されることはありませんが、常に参照変数によって渡されるか割り当てられます。(Javaのメソッドは常に値渡しですが、渡されるのは参照変数の値です。) [11] Java仮想マシンはガベージコレクションを管理し、オブジェクトがアクセスできなくなった後にクリーンアップします。Javaでは、特定のオブジェクトを自動的にコピーする方法はありません。
コピーは通常、クラスのclone() メソッドによって実行されます。このメソッドは通常、コピーを取得するために親クラスの clone() メソッドを呼び出し、その後カスタム コピー手順を実行します。最終的に、これはObject(最上位クラスの) clone() メソッドに到達し、オブジェクトと同じクラスの新しいインスタンスを作成し、すべてのフィールドを新しいインスタンスにコピーします (「浅いコピー」)。このメソッドを使用する場合、クラスはCloneableマーカー インターフェイスを実装する必要があります。そうしないと、「クローンがサポートされていない例外」がスローされます。親クラスからコピーを取得した後、クラス独自の clone() メソッドは、ディープ コピー (つまり、オブジェクトによって参照される構造の一部を複製する) や新しいインスタンスに新しい一意の ID を与えるなどのカスタム クローン機能を提供できます。
clone() の戻り値の型は ですObjectが、Java は共変戻り値の型をサポートしているため、clone メソッドの実装者は代わりに複製されるオブジェクトの型を記述できます。clone() を使用する利点の 1 つは、オーバーライド可能なメソッドであるため、どのオブジェクトでも clone() を呼び出すことができ、呼び出し元のコードでそのクラスが何であるかを知る必要がなく (コピー コンストラクターでは必要)、そのクラスの clone() メソッドが使用されることです。
欠点は、抽象型の clone() メソッドにアクセスできないことが多いことです。Java のほとんどのインターフェースと抽象クラスでは、public clone() メソッドが指定されていません。したがって、多くの場合、clone() メソッドを使用する唯一の方法は、オブジェクトのクラスがわかっている場合であり、これは、可能な限り最も一般的な型を使用するという抽象化の原則に反しています。たとえば、Java で List 参照がある場合、List には public clone() メソッドが指定されていないため、その参照で clone() を呼び出すことはできません。Array List や Linked List などの List の実装には通常、clone() メソッドがありますが、オブジェクトのクラス型を持ち歩くのは不便であり、抽象化としては不適切です。
Java でオブジェクトをコピーする別の方法は、インターフェイスを介してオブジェクトをシリアル化するSerializableことです。これは通常、永続性とワイヤ プロトコルの目的で使用されますが、オブジェクトのコピーを作成し、clone とは異なり、オブジェクトの循環グラフを適切に処理するディープ コピーを、プログラマーの最小限の労力ですぐに利用できます。
これらの方法は両方とも、クローンやシリアル化でコピーされたオブジェクトにはコンストラクタが使用されないfinalという顕著な問題を抱えています。これにより、不適切に初期化されたデータによるバグが発生したり、メンバーフィールドの使用が妨げられたり、メンテナンスが困難になったりする可能性があります。一部のユーティリティでは、リフレクションを使用してオブジェクトをディープコピーすることで、これらの問題を克服しようとしています。たとえば、ディープクローニングライブラリなどです。[12]
エッフェル塔
Eiffelのランタイム オブジェクトは、参照を介して間接的にアクセスすることも、フィールドがそれを使用するオブジェクト内に埋め込まれている拡張オブジェクトとしてアクセスすることもできます。つまり、オブジェクトのフィールドは外部または内部に格納されます。
Eiffel クラスにはANY、オブジェクトの浅いコピーと深いコピー、およびクローン作成の機能が含まれています。すべての Eiffel クラスは を継承しているANYため、これらの機能はすべてのクラス内で使用でき、参照オブジェクトと展開オブジェクトの両方に適用できます。
このcopy機能は、あるオブジェクトから別のオブジェクトへのフィールド単位の浅いコピーを実行します。この場合、新しいオブジェクトは作成されません。 が にyコピーされた場合、の適用前にxによって参照されていた同じオブジェクトは、機能が完了した後もによって参照されます。
ycopyxcopy
の浅い複製である新しいオブジェクトを作成するにはy、 機能twinを使用します。この場合、ソースのフィールドと同一のフィールドを持つ 1 つの新しいオブジェクトが作成されます。
機能はtwin機能 に依存しておりcopy、必要に応じて の子孫で再定義できますANY。 の結果は、twinアンカー型 になりますlike Current。
ディープ コピーとディープ ツインの作成は、クラス から継承された機能deep_copyとを使用して実行できます。これらの機能は、オブジェクト構造全体のすべてのオブジェクトを複製するため、多くの新しいオブジェクトを作成する可能性があります。既存のオブジェクトへの参照を単にコピーするのではなく、新しい複製オブジェクトが作成されるため、ディープ操作は、シャロー操作よりもパフォーマンスの問題の原因になりやすくなります。
deep_twinANY
他の言語
C#では、インターフェイスを使用する代わりにICloneable、ジェネリック拡張メソッドを使用して、リフレクションを使用したディープ コピーを作成できます。これには 2 つの利点があります。まず、コピーする各プロパティと変数を手動で指定しなくても、すべてのオブジェクトをコピーできる柔軟性が得られます。次に、型がジェネリックであるため、コンパイラによって、コピー先オブジェクトとコピー元オブジェクトの型が同じであることが保証されます。
Objective-Cでは、メソッドcopyとがすべてのオブジェクトに継承され、コピーを実行するために使用されます。後者は、元のオブジェクトの可変型を作成するためのものです。これらのメソッドは、それぞれメソッドとメソッドをmutableCopy呼び出してコピーを実行します。オブジェクトがコピー可能になるには、対応するメソッドを実装する必要があります。
copyWithZonemutableCopyWithZonecopyWithZone
OCamlでは、ライブラリ関数 Oo.copy がオブジェクトの浅いコピーを実行します。
Pythonでは、ライブラリのコピーモジュールは、関数copy()とdeepcopy()関数を通じて、それぞれオブジェクトの浅いコピーと深いコピーを提供します。[13]__copy__()プログラマーは、オブジェクト内に特別なメソッドとを定義して、カスタムのコピー実装を提供することができます__deepcopy__()。
Rubyでは、すべてのオブジェクトが浅いコピーを実行するための 2 つのメソッド、clone と dup を継承しています。この 2 つのメソッドの違いは、 がcloneオブジェクトの汚染された状態、凍結された状態、およびオブジェクトが持つ可能性のあるすべてのシングルトンdupメソッドをコピーするのに対し、 が汚染された状態のみをコピーする点です。ディープ コピーは、オブジェクトのバイト ストリームまたは YAML シリアル化をダンプしてロードすることで実現できます。[1] または、 deep_dive gem を使用して、オブジェクト グラフの制御されたディープ コピーを実行することもできます。[2]
Perlでは、ネストされた構造は参照を使用して保存されるため、開発者は構造全体をループしてデータを再参照するか、dclone()モジュール Storable の関数を使用することができます。
VBAでは、 型の変数の割り当てはObject浅いコピーであり、他のすべての型 (数値型、文字列、ユーザー定義型、配列) の割り当ては深いコピーです。したがって、Set割り当てのキーワードは浅いコピーを示し、(オプションの) キーワードはLet深いコピーを示します。VBA には、オブジェクトの深いコピー用の組み込みメソッドはありません。
参照
注記
- ^ abc グロゴーノ & サッキネン 2000.
- ^ Goldberg & Robson 1983、pp. 97–99。「オブジェクトのコピーを作成する方法は 2 つあります。違いは、オブジェクトの変数の値がコピーされるかどうかです。値がコピーされない場合は共有されます (
shallowCopy)。値がコピーされた場合は共有されません (deepCopy)。」 - ^ 「C++ の浅いコピーと深いコピーの説明」。
- ^ 「.NET の浅いコピーと深いコピーの説明」。
- ^ 「Generic Shallow vs Deep Copy Explanation」。2016年3月4日時点のオリジナルよりアーカイブ。 2013年4月10日閲覧。
- ^ コア Java: 基礎、第 1 巻、p. 295
- ^ Effective Java、第2版、54ページ
- ^ 「Object.clone() によって実行されるフィールドごとのコピーとは何ですか?」、Stack Overflow
- ^ 「Josh Bloch のデザインについて: Effective Java の著者 Josh Bloch との対談」、Bill Venners 著、JavaWorld、2002 年 1 月 4 日、13 ページ
- ^ Goldberg & Robson 1983、p. 97。「 のデフォルトの実装
copyは です。コピーによって共有変数と共有されていない変数の特別な組み合わせが生成されなければならないサブクラスでは、またはshallowCopyに関連付けられたメソッドではなく、 copy に関連付けられたメソッドが通常再実装されます。」shallowCopydeepCopy - ^ 「メソッドまたはコンストラクターに情報を渡す」。2013年10 月 8 日閲覧。
- ^ Java ディープクローニングライブラリ
- ^ Python コピーモジュール
参考文献
- ゴールドバーグ、アデル、ロブソン、デビッド (1983)。Smalltalk -80: 言語とその実装。カリフォルニア州パロアルト: ゼロックス パロアルト リサーチ センター。ISBN 978-0-201-11371-6。
- Grogono, Peter; Sakkinen, Markku (2000 年 5 月 12 日)。「コピーと比較: 問題と解決策」(PDF)。Elisa Bertino (編) 著。Lecture Notes in Computer Science。ECOOP 2000 — オブジェクト指向プログラミング。第 1850 巻。Springer Berlin Heidelberg。pp. 226– 250。doi :10.1007/3-540-45102-1_11。2015年 6 月 23日閲覧。
