ディープコピーとシャローコピーの違いは何ですか?
31 に答える
幅と深さ。オブジェクトをルートノードとする参照ツリーの観点から考えてください。
浅い:
変数 A と B はメモリの異なる領域を参照します。B が A に割り当てられている場合、2 つの変数はメモリの同じ領域を参照します。コンテンツを共有しているため、後でいずれかのコンテンツを変更すると、他方のコンテンツに即座に反映されます。
深い:
変数 A と B はメモリの異なる領域を参照します。B が A に割り当てられると、A が指すメモリ領域の値が B が指すメモリ領域にコピーされます。いずれかのコンテンツに対するその後の変更は、A または B に固有のままです。内容は共有されません。
浅いコピーは重複をできるだけ少なくします。コレクションの浅いコピーは、要素ではなくコレクション構造のコピーです。浅いコピーにより、2 つのコレクションが個々の要素を共有するようになりました。
ディープ コピーはすべてを複製します。コレクションのディープ コピーは、元のコレクションのすべての要素が複製された 2 つのコレクションです。
要するに、それは何が何を指しているかに依存します。浅いコピーでは、オブジェクトBはメモリ内のオブジェクトAの場所を指します。ディープコピーでは、オブジェクトAのメモリ位置にあるすべてのものがオブジェクトBのメモリ位置にコピーされます。
このウィキの記事には素晴らしい図があります。
次の画像を検討してみてください
たとえば、Object.MemberwiseCloneは浅いコピーリンクを作成します。
ここで説明されているように、 ICloneableインターフェイスを使用してディープコピーを取得できます
特に iOS 開発者向け:
B
が の浅いコピーである場合A
、プリミティブ データの場合は のようB = [A assign];
になり、オブジェクトの場合は のようになりB = [A retain]
ます。
B と A が同じメモリ位置を指している
B
が のディープ コピーである場合、次のA
ようになります。B = [A copy];
B と A は異なるメモリ位置を指す
B メモリ アドレスは A と同じです
BはAと同じ内容
浅いコピー: メンバー値をあるオブジェクトから別のオブジェクトにコピーします。
ディープ コピー: メンバー値をあるオブジェクトから別のオブジェクトにコピーします。
すべてのポインター オブジェクトが複製され、ディープ コピーされます。
例:
class String
{
int size;
char* data;
};
String s1("Ace"); // s1.size = 3 s1.data=0x0000F000
String s2 = shallowCopy(s1);
// s2.size =3 s2.data = 0X0000F000
String s3 = deepCopy(s1);
// s3.size =3 s3.data = 0x0000F00F
// (With Ace copied to this location.)
ここで短くてわかりやすい答えを見たことがないので、試してみます。
シャロー コピーでは、コピー元が指すオブジェクトはコピー先も指す (そのため、参照されるオブジェクトはコピーされません)。
ディープ コピーでは、コピー元が指すオブジェクトがコピーされ、そのコピーがコピー先によって指されます (したがって、参照される各オブジェクトが 2 つ存在することになります)。これは、オブジェクト ツリーを下に再帰します。
{2 つのオブジェクトを想像してください: 同じ型 _t の A と B (C++ に関して) で、A を B に浅い/深いコピーすることを考えています}
浅いコピー: A への参照のコピーを B に作成するだけです。これは、A のアドレスのコピーと考えてください。したがって、A と B のアドレスは同じになります。つまり、同じメモリ位置、つまりデータの内容を指します。
ディープ コピー: A のすべてのメンバーのコピーを作成し、B の別の場所にメモリを割り当ててから、コピーされたメンバーを B に割り当ててディープ コピーを実現します。このように、A が存在しなくなっても、B はメモリ内で有効です。使用する正しい用語はクローン作成です。この場合、両者はまったく同じですが、異なる (つまり、メモリ空間に 2 つの異なるエンティティとして格納される) ことがわかっています。ディープ コピー中に選択するプロパティを包含/除外リストから決定できるクローン ラッパーを提供することもできます。これは、API を作成するときの非常に一般的な方法です。
関連する問題を理解している場合にのみ、シャローコピーを実行することを選択できます。C++ や C で処理するポインターが膨大な数ある場合、オブジェクトの浅いコピーを行うのは本当に悪い考えです。
EXAMPLE_OF_DEEP COPY_例として、画像処理とオブジェクト認識を行う場合、処理領域から「無関係で反復的なモーション」をマスクする必要があります。イメージ ポインターを使用している場合は、それらのマスク イメージを保存するための仕様がある可能性があります。今...イメージの浅いコピーを行うと、ポインター参照がスタックから削除されたときに、参照とそのコピーが失われます。つまり、ある時点でアクセス違反のランタイムエラーが発生します。この場合、必要なのはイメージのクローン作成によるディープ コピーです。このようにして、将来マスクが必要になった場合にマスクを取得できます。
EXAMPLE_OF_SHALLOW_COPY私は StackOverflow のユーザーに比べて非常に知識が豊富ではないので、この部分を削除して、明確にすることができれば良い例を挙げてください。しかし、プログラムが無限の期間実行されることがわかっている場合、つまり、関数呼び出しによるスタックに対する連続的な「プッシュ/ポップ」操作は、浅いコピーを行うのは良い考えではないと本当に思います。アマチュアや初心者向けのデモンストレーション (C/C++ のチュートリアルなど) であれば、おそらく問題ありません。しかし、監視および検出システム、またはソナー追跡システムなどのアプリケーションを実行している場合は、遅かれ早かれプログラムを強制終了するため、オブジェクトを浅いコピーし続けることは想定されていません。
char * Source = "Hello, world.";
char * ShallowCopy = Source;
char * DeepCopy = new char(strlen(Source)+1);
strcpy(DeepCopy,Source);
「ShallowCopy」は、「Source」と同じメモリ内の場所を指します。「DeepCopy」はメモリ内の別の場所を指していますが、内容は同じです。
シャローコピーとは?
浅いコピーは、オブジェクトのビット単位のコピーです。元のオブジェクトの値の正確なコピーを持つ新しいオブジェクトが作成されます。オブジェクトのいずれかのフィールドが他のオブジェクトへの参照である場合、参照アドレスのみがコピーされます。つまり、メモリ アドレスのみがコピーされます。
この図では、 にはint 型と 型のフィールドがありMainObject1
ます。の浅いコピーを行うと、コピーされた値を含み、それ自体を指しているが作成されます。はプリミティブ型であるため、その値は にコピーされますが、はオブジェクトであるため、引き続き を指すことに注意してください。したがって、 に加えた変更はすべてに反映されます。field1
ContainObject1
ContainObject
MainObject1
MainObject2
field2
field1
ContainObject1
field1
field2
ContainedObject1
MainObject2
ContainObject1
ContainObject1
MainObject1
MainObject2
これが浅いコピーである場合、深いコピーとは何かを見てみましょう。
ディープコピーとは?
ディープ コピーはすべてのフィールドをコピーし、フィールドが指す動的に割り当てられたメモリのコピーを作成します。ディープ コピーは、オブジェクトが参照先のオブジェクトと共にコピーされるときに発生します。
この図では、MainObject1 にfield1
int 型と 型のフィールドがありContainObject1
ますContainObject
。のディープ コピーを実行するとMainObject1
、のコピーされた値と のコピーされた値を含むMainObject2
が作成されます。で行った変更は、には反映されないことに注意してください。field2
field1
ContainObject2
ContainObject1
ContainObject1
MainObject1
MainObject2
オブジェクト指向プログラミングでは、型にはメンバー フィールドのコレクションが含まれます。これらのフィールドは、値または参照 (つまり、値へのポインター) のいずれかによって格納できます。
浅いコピーでは、型の新しいインスタンスが作成され、値が新しいインスタンスにコピーされます。参照ポインタも値と同様にコピーされます。したがって、参照は元のオブジェクトを指しています。参照によって格納されたメンバーへの変更は、参照されたオブジェクトのコピーが作成されていないため、元のオブジェクトとコピーの両方に表示されます。
ディープ コピーでは、値によって格納されるフィールドは以前のようにコピーされますが、参照によって格納されるオブジェクトへのポインターはコピーされません。代わりに、参照先オブジェクトのディープ コピーが作成され、新しいオブジェクトへのポインターが格納されます。これらの参照オブジェクトに加えられた変更は、オブジェクトの他のコピーには影響しません。
var source = { firstName="Jane", lastname="Jones" };
var shallow = ShallowCopyOf(source);
var deep = DeepCopyOf(source);
source.lastName = "Smith";
WriteLine(source.lastName); // prints Smith
WriteLine(shallow.lastName); // prints Smith
WriteLine(deep.lastName); // prints Jones
浅いコピー- 元のオブジェクトと浅いコピーされたオブジェクト内の参照変数には、共通のオブジェクトへの参照があります。
ディープ コピー- 元のオブジェクトとディープ コピーされたオブジェクト内の参照変数は、異なるオブジェクトを参照しています。
clone は常に浅いコピーを行います。
public class Language implements Cloneable{
String name;
public Language(String name){
this.name=name;
}
public String getName() {
return name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
メインクラスは次のとおりです-
public static void main(String args[]) throws ClassNotFoundException, CloneNotSupportedException{
ArrayList<Language> list=new ArrayList<Language>();
list.add(new Language("C"));
list.add(new Language("JAVA"));
ArrayList<Language> shallow=(ArrayList<Language>) list.clone();
//We used here clone since this always shallow copied.
System.out.println(list==shallow);
for(int i=0;i<list.size();i++)
System.out.println(list.get(i)==shallow.get(i));//true
ArrayList<Language> deep=new ArrayList<Language>();
for(Language language:list){
deep.add((Language) language.clone());
}
System.out.println(list==deep);
for(int i=0;i<list.size();i++)
System.out.println(list.get(i)==deep.get(i));//false
}
上記の出力は-
偽 真 真
偽偽偽
元のオブジェクトに加えられた変更は、深いオブジェクトではなく浅いオブジェクトに反映されます。
list.get(0).name="ViSuaLBaSiC";
System.out.println(shallow.get(0).getName()+" "+deep.get(0).getName());
出力 - VisualBaSiC C
簡単に言えば、浅いコピーは参照による呼び出しに似ており、深いコピーは値による呼び出しに似ています
参照渡しでは、関数の仮パラメータと実パラメータの両方が同じメモリ位置と値を参照します。
値による呼び出しでは、関数の仮パラメーターと実際のパラメーターの両方が異なるメモリ位置を参照しますが、値は同じです。
struct sample
{
char * ptr;
}
void shallowcpy(sample & dest, sample & src)
{
dest.ptr=src.ptr;
}
void deepcpy(sample & dest, sample & src)
{
dest.ptr=malloc(strlen(src.ptr)+1);
memcpy(dest.ptr,src.ptr);
}
他の回答にさらに追加するには、
- オブジェクトの浅いコピーは、値型ベースのプロパティの値によるコピーと、参照型ベースのプロパティの参照によるコピーを実行します。
- オブジェクトのディープ コピーは、値型ベースのプロパティの値によるコピーと、(参照型の) 階層の深い参照型ベースのプロパティの値によるコピーを実行します。
浅いコピーでは新しい参照は作成されませんが、深いコピーでは新しい参照が作成されます。
深いコピーと浅いコピーを説明するプログラムは次のとおりです。
public class DeepAndShollowCopy {
int id;
String name;
List<String> testlist = new ArrayList<>();
/*
// To performing Shallow Copy
// Note: Here we are not creating any references.
public DeepAndShollowCopy(int id, String name, List<String>testlist)
{
System.out.println("Shallow Copy for Object initialization");
this.id = id;
this.name = name;
this.testlist = testlist;
}
*/
// To performing Deep Copy
// Note: Here we are creating one references( Al arraylist object ).
public DeepAndShollowCopy(int id, String name, List<String> testlist) {
System.out.println("Deep Copy for Object initialization");
this.id = id;
this.name = name;
String item;
List<String> Al = new ArrayList<>();
Iterator<String> itr = testlist.iterator();
while (itr.hasNext()) {
item = itr.next();
Al.add(item);
}
this.testlist = Al;
}
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Oracle");
list.add("C++");
DeepAndShollowCopy copy=new DeepAndShollowCopy(10,"Testing", list);
System.out.println(copy.toString());
}
@Override
public String toString() {
return "DeepAndShollowCopy [id=" + id + ", name=" + name + ", testlist=" + testlist + "]";
}
}
私は次の行から理解するようになりました。
浅いコピーは、オブジェクトの値の型(int、float、bool) フィールドをターゲット オブジェクトにコピーし、オブジェクトの参照型 (文字列、クラスなど) はターゲット オブジェクトの参照としてコピーされます。このターゲット参照型では、ソース オブジェクトのメモリ ロケーションを指します。
ディープ コピーは、オブジェクトの値と参照型をターゲット オブジェクトの完全な新しいコピーにコピーします。これは、値型と参照型の両方に新しいメモリ ロケーションが割り当てられることを意味します。
[ブログ] から引用: http://sickprogrammersarea.blogspot.in/2014/03/technical-interview-questions-on-c_6.html
ディープ コピーでは、1 つのオブジェクトのコンテンツを使用して、同じクラスの別のインスタンスを作成します。ディープ コピーでは、2 つのオブジェクトに同じ情報が含まれる場合がありますが、ターゲット オブジェクトには独自のバッファーとリソースがあります。いずれかのオブジェクトが破壊されても、残りのオブジェクトには影響しません。オーバーロードされた代入演算子は、オブジェクトのディープ コピーを作成します。
浅いコピーでは、1 つのオブジェクトの内容を同じクラスの別のインスタンスにコピーして、ミラー イメージを作成します。参照とポインタを直接コピーするため、2 つのオブジェクトは、外部に含まれる他のオブジェクトの同じコンテンツを共有し、予測不能になります。
説明:
コピー コンストラクターを使用して、データ値をメンバーごとに単純にコピーします。このコピー方法は、浅いコピーと呼ばれます。オブジェクトが単純なクラスであり、組み込み型で構成され、ポインターがない場合、これは許容されます。この関数は値とオブジェクトを使用し、その動作は浅いコピーでは変更されません。メンバーであるポインターのアドレスのみがコピーされ、アドレスが指している値はコピーされません。オブジェクトのデータ値は、関数によって誤って変更される可能性があります。関数がスコープ外になると、オブジェクトのコピーとそのすべてのデータがスタックからポップされます。
オブジェクトにポインタがある場合は、ディープ コピーを実行する必要があります。オブジェクトのディープ コピーでは、フリー ストア内のオブジェクトにメモリが割り当てられ、ポイントされている要素がコピーされます。関数から返されるオブジェクトにはディープ コピーが使用されます。
浅いコピーとは、新しいオブジェクトを作成してから、現在のオブジェクトの非静的フィールドを新しいオブジェクトにコピーすることです。フィールドが値型の場合 --> フィールドのビットごとのコピーが実行されます。参照型の場合--> 参照はコピーされますが、参照されるオブジェクトはコピーされません。したがって、元のオブジェクトとそのクローンは同じオブジェクトを参照します。
ディープコピーは、新しいオブジェクトを作成し、現在のオブジェクトの非静的フィールドを新しいオブジェクトにコピーします。フィールドが値型の場合--> フィールドのビットごとのコピーが実行されます。フィールドが参照型の場合--> 参照されたオブジェクトの新しいコピーが実行されます。複製するクラスは、[Serializable] としてフラグを立てる必要があります。
配列のコピー:
配列はクラスです。つまり、参照型であるため、array1 = array2 は同じ配列を参照する 2 つの変数になります。
しかし、この例を見てください:
static void Main()
{
int[] arr1 = new int[] { 1, 2, 3, 4, 5 };
int[] arr2 = new int[] { 6, 7, 8, 9, 0 };
Console.WriteLine(arr1[2] + " " + arr2[2]);
arr2 = arr1;
Console.WriteLine(arr1[2] + " " + arr2[2]);
arr2 = (int[])arr1.Clone();
arr1[2] = 12;
Console.WriteLine(arr1[2] + " " + arr2[2]);
}
浅い複製とは、複製された配列によって表されるメモリのみがコピーされることを意味します。
配列に値型オブジェクトが含まれている場合、値はコピーされます。
配列に参照型が含まれている場合、参照のみがコピーされます。その結果、メンバーが同じオブジェクトを参照する 2 つの配列が存在します。
ディープ コピーを作成するには (参照型が複製されている場合)、配列をループして各要素を手動で複製する必要があります。
コピー コンストラクターは、同じクラスの以前に作成されたオブジェクトで新しいオブジェクトを初期化するために使用されます。デフォルトでは、コンパイラは浅いコピーを書きました。動的メモリ割り当てが関係していない場合、浅いコピーは正常に機能します。これは、動的メモリ割り当てが関係している場合、両方のオブジェクトがヒープ内の同じメモリ位置を指すためです。したがって、この問題を解決するために、両方のオブジェクトが独自の属性のコピーを持つようにディープ コピーを作成しました。思い出に。完全な例と説明を含む詳細を読むには、記事C++ コンストラクターを参照してください。
浅いコピーと単純に新しい変数名をリストに割り当てることの間の混乱のために、もう少し追加します。
「私たちが持っているとしましょう:
x = [
[1,2,3],
[4,5,6],
]
このステートメントは、2 つの内部リストと 1 つの外部リストの 3 つのリストを作成します。外部リストへの参照は、x という名前で利用可能になります。もしそうなら
y = x
データはコピーされません。メモリのどこかにまだ同じ 3 つのリストがあります。これにより、外側のリストが以前の名前 x に加えて y という名前で使用できるようになっただけです。もしそうなら
y = list(x)
また
y = x[:]
これにより、x と同じ内容の新しいリストが作成されます。リスト x には 2 つの内部リストへの参照が含まれていたため、新しいリストにも同じ 2 つの内部リストへの参照が含まれます。コピーされるリストは 1 つだけで、外側のリストです。現在、メモリには 4 つのリストがあります。2 つの内側のリスト、外側のリスト、および外側のリストのコピーです。元の外部リストは x という名前で利用でき、新しい外部リストは y という名前で利用できるようになります。
内部リストはコピーされていません! この時点で、x または y のいずれかから内部リストにアクセスして編集できます。
2 次元 (またはそれ以上) のリスト、または任意の種類のネストされたデータ構造があり、すべての完全なコピーを作成する場合は、copy モジュールで deepcopy() 関数を使用します。外部リストのアイテムを反復処理してそれぞれのコピーを作成し、すべての内部コピーの新しい外部リストを作成するため、ソリューションは 2-D リストでも機能します。」
上記のすべての定義に加えて、最も一般的に使用されるもう 1 つのディープ コピーは、クラスのコピー コンストラクター (またはオーバーロード代入演算子) にあります。
浅いコピー --> は、コピー コンストラクターを提供していない場合です。ここでは、オブジェクトのみがコピーされますが、クラスのすべてのメンバーがコピーされるわけではありません。
ディープ コピー --> は、クラスにコピー コンストラクターまたはオーバーロード割り当てを実装することを決定し、クラスのすべてのメンバーをコピーできるようにする場合です。
MyClass& MyClass(const MyClass& obj) // copy constructor for MyClass
{
// write your code, to copy all the members and return the new object
}
MyClass& operator=(const MyClass& obj) // overloading assignment operator,
{
// write your code, to copy all the members and return the new object
}