shiracamus
@shiracamus (しらかみゅ)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

オブジェクトを代入した参照型変数を別の変数に単純代入した場合もシャローコピーと言うのでしょうか?

結論

オブジェクトを代入した参照型変数を別の変数に代入した場合はシャローコピーではありません。

理由

@mogamoga1337 さんのコメント にある sharrow copy の説明リンク先

  1. They are not the same object (o1 !== o2).

と書いてあり、単純代入した場合は o1 === o2 であるため、シャローコピーではない。

JavaScript
> let obj1 = { 'name': 'Taro' };
undefined
> let obj2 = obj1;           // 単純代入
undefined
> let obj3 = { ...obj1 };    // スプレッド構文
undefined
> obj1 !== obj2              // 単純代入はシャローコピーではない
false
> obj1 !== obj3              // スプレッド構文はシャローコピー
true

意見交換趣旨

参照型変数を別の変数に単純代入することをシャローコピーと書いてあるQiita記事やブログが散見されますが、それが正しいのか間違いのか意見交換したいです。

言語やデータ型によって異なるというコメントをいただきましたので、参照型変数・参照型オブジェクトに限定します。

私は、オブジェクト自体をコピーする(新たなオブジェクトを生成して内容をコピーする)方法としてシャローコピーかディープコピーかという言葉を使うのだと理解していました。
Wikipediaの「Object copying」にも、「In that case a new object B is created(新たなオブジェクトBを生成する)」と書いてあるのですが、Object copying 以外に代入にも使用する言葉なのかはわかりませんでした。

オブジェクトを浅く(1階層だけ)コピーするのがシャローコピーで、単純代入とは影響範囲が異なります。

種類 1層目への代入 深い層への代入
単純代入(参照のコピー) 影響する 影響する
シャローコピー 影響しない 影響する
ディープコピー 影響しない 影響しない

どなたか「単純代入もシャローコピー」あるいは「単純代入はシャローコピーとは言わない」などと書いてある情報処理の教科書や各種言語の公式ドキュメントをご存じの方はいらっしゃいませんでしょうか?
ちなみに、ChatGPTに聞いてみたのですが「代入はシャローコピーの一種」と返答されました。

参考: https://developer.mozilla.org/ja/docs/Glossary/Shallow_copy

オブジェクトのシャローコピーとは、コピーがコピー元のオブジェクトとプロパティにおいて同じ参照を共有する(同じ基礎値を指す)コピーのことを指します。

シャローコピーの例
let ingredients_list = ["noodles", { list: ["eggs", "flour", "water"] }];
let ingredients_list_copy = Array.from(ingredients_list);

シャローコピーの説明ではArray.fromで新たな配列を作って内容をコピーしています。
ですが、以下のように新たなオブジェクトを作らない単純代入したコードをシャローコピーの例として書いてあるQiita記事やブログを見かけます。

単純代入の例
let ingredients_list = ["noodles", { list: ["eggs", "flour", "water"] }];
let ingredients_list_copy = ingredients_list;

単純代入もシャローコピーと言うのは間違いだと思うのですが、みなさんのご意見はいかがでしょうか?
image.png

2

オブジェクトのコピーに使う用語だとわかりますが、代入にも使うのか分からないのです。
「代入に使うのは間違い」と指摘できる根拠があるといいのですが。

0Like

シャローコピーとは参照だけを複製するという意味で、参照先のオブジェクトも複製する場合はディープコピー・・・という説明でわかりませんか?

0Like

シャローコピーとは参照だけを複製する

参照だけを複製する」という記述はどこにありますでしょうか?

私の認識は、

  • シャローコピーは、新しいオブジェクトを作り(変数は別々のオブジェクトを参照)、コピー元オブジェクト内のプロパティ(プリミティブ値や参照)をそのまま(無加工)コピーし、オブジェクト内の参照の先の深い層のオブジェクトはコピーせずに共有する
  • ディープコピーは、深い層も新しいオブジェクトを作ってコピーする
  • 代入は、オブジェクトへの参照をコピーするだけでオブジェクトの生成・コピーは一切しない

です。
MDNの説明でも単純代入ではなく

let ingredients_list_copy = Array.from(ingredients_list);

で新しい配列を生成してシャローコピーしています。

0Like

「参照だけを複製する」という記述はどこにありますでしょうか?

私の言葉で書いたもので MDN の記事などにはその記述そのものはないですが、MDN の記事の、

"オブジェクトのシャローコピーとは、コピーがコピー元のオブジェクトとプロパティにおいて同じ参照を共有する(同じ基礎値を指す)コピーのことを指します"

の「同じ参照を共有する」と同義のつもりです。

0Like

MDN の記事のコードは、

// 2 つの要素を持つ配列を生成、文字列と JavaScript オブジェクトを作成、配列
// の要素のそれぞれに文字列と JavaScript オブジェクトへのアドレスを代入、
// 完成した配列のアドレスを変数 ingredients_list に代入。
let ingredients_list = ["noodles", { list: ["eggs", "flour", "water"] }];

// Array.from メソッドで ingredients_list が指す配列のシャローコピーを作成、
// シャローコピーのアドレスを変数 ingredients_list_copy に代入。
let ingredients_list_copy = Array.from(ingredients_list);

ということだと理解していますが、質問者さんが書かれた、

単なる変数代入をシャローコピーと書いてあるQiita記事やブログが散見されます
ChatGPTに聞いてみたのですが「代入はシャローコピーの一種」と答えが返ってきました。

は、

let ingredients_list = ["noodles", { list: ["eggs", "flour", "water"] }];
let ingredients_list_copy = ingredients_list;

もシャローコピーと言っているようだが違うのではないか・・・ということでしょうか?

0Like
  1. オブジェクト内プロパティにはプリミティブ値や参照が混在しうる
  2. シャローコピーでは、コピー先オブジェクトを生成し、コピー元オブジェクト内のすべてのプロパティをコピー先オブジェクト内にコピーする
  3. オブジェクト内の参照(深い層のオブジェクトへの参照)は同じであり、同じオブジェクトを共有する

という認識は同じですか?
単純代入では 2. が行われませんが、単純代入もシャローコピーと言うのかが私の疑問点です。
image.png

0Like

単純代入もシャローコピーと言うのかが私の疑問点です。

上に私が書いた、

let ingredients_list = ["noodles", { list: ["eggs", "flour", "water"] }];
let ingredients_list_copy = ingredients_list;

もシャローコピーと言っているようだが違うのではないか・・・ということを議論するのではダメなんですか?

0Like

もシャローコピーと言っているようだが違うのではないか・・・ということを議論するのではダメなんですか?

コメントを書いている間にコメント追記されていました。失礼しました。
それを議論したいです。

0Like

私は浅いも深いもオブジェクトをコピーする後付の概念と理解してます。

そもそも、変数の代入は浅いも深いも無く、変数の型が定義しているサイズ分をコピーしています。
型によって、値だったり、アドレスだっりするだけです。

C言語のポインターを勉強すると理解が早いとおもいます。

2Like

それを議論したいです。

では個人的意見を書きますと、

let ingredients_list = ["noodles", { list: ["eggs", "flour", "water"] }];
let ingredients_list_copy = ingredients_list;

の 2 行目は、オブジェクトのアドレスを単に別の変数に代入しただけで、少なくとも Array.from メソッドで作るような配列のシャローコピーを作るのとは違うとは言えると思います。

3Like

例えば深いコピーの関数を dcopy とします。Array.fromと読み替えてもokです。

let ingredients_list = ["noodles", { list: ["eggs", "flour", "water"] }];
let ingredients_list_copy = ingredients_list;

let ingredients_list = ["noodles", { list: ["eggs", "flour", "water"] }];
let ingredients_list_copy = dcopy(ingredients_list);

も、代入されるのは型を宣言した際の長さのアドレスがコピーされます。

ここで、dcopyの振る舞いを理解できないと、世の中の多くの方が誤った見解を持つように、深い浅いを強く意識する罠にはまります。

dcopy(Array.from)はメモリ空間を確保(アロケーション)して、ingredients_listの構造を解析して、確保したメモリ空間に値を転送します。全ての構造と値の転送が完了したら、メモリ空間の先頭のアドレスを左辺の変数ingredients_list_copyに代入する =に渡します。

dcopyは深い、浅いを意識してプログラ厶されますが、代入は深いも浅いも全く関係なく、代入されるのは型を宣言した際の長さのアドレスがコピーされます。

ChatGPTに聞いてみたのですが「代入はシャローコピーの一種」と返答されました。
最後に、ChatGPTを擁護する訳ではないですが、世の中の多くの方が誤った見解を持っており、ChatGPTはそれを総括して回答してます。

0Like

Swift には値型の配列 Array と参照型の配列 NS(Mutable)Array が存在し、代入によって1階層目のコピーが発生する・しないが異なります。

import Foundation

let a = [1, 2]
var b = a

b.append(3)

print(a) // => [1, 2] // 元の配列は変化しない=1階層目のコピーが起きている
print(b) // => [1, 2, 3]

let c: NSMutableArray = [1, 2]
let d: NSMutableArray = c

d.add(3)

print(c) // => (1, 2, 3) // 元の配列も変化する=1階層目のコピーが起きていない
print(d) // => (1, 2, 3)

“単なる”代入がコピーを伴うかどうかは言語やそのデータ型によって異なる場合があるため、一概には言えないと思います。なお、根拠を示せない私見でいえば、1階層目のコピーを伴わない単純代入はシャローコピーとは言わないと思います(循環論法な気もしますが)。

2Like

Python の場合:

Assignment statements in Python do not copy objects,

「Python における代入文はオブジェクトをコピーしない」と書いてあります.
そもそもコピーですらないということから,「Python における代入はシャローコピーとは異なる」と言えそうです.
(私見として,そのオブジェクトを参照する名前が増えるだけであって,
そのオブジェクトが保持しているデータが複製されないならば,「そもそもコピーではない」というのはしっくりきます.)

ただし,代入時に値をコピーする言語もあるので,
「(言語を問わず)代入はシャローコピーやディープコピーとは異なる」と言うことはできないと思います.

1Like

みなさんありがとうございます。
参照型変数に限定しないといけませんね。

  • 値型オブジェクトの場合、代入でオブジェクトをコピーするのでシャローコピーかディープコピーが行われる
  • 参照型オブジェクトの場合、代入でオブジェクトをコピーしないのでシャローコピーという言葉を使わない
0Like

そもそも代入とコピーするしないは別の問題なので、そこを混ぜると訳の分からない事になるんじゃないでしょうか。各言語仕様による、としか言えません。
演算子のオーバーロードが許されている言語では、見た目が代入でも別の処理をやっているということもあります。また、文字列は参照型でも扱いが特殊化されている事もあります。
言語を指定した上で、具体的なケースを見ないと一概に判断出来ないです。

1Like

値型オブジェクトの場合、代入でオブジェクトをコピーするのでシャローコピーかディープコピーが行われる

参照型オブジェクトの場合、代入でオブジェクトをコピーしないのでシャローコピーという言葉を使わない

質問者さんは「代入」=「コピー」もしくは「代入」の過程でコピーがあるという考えで、それがシャローコピーになるのかディープコピーになるのかという話がしたいのでしょうか?

つまり、前に書いた例、

let ingredients_list = ["noodles", { list: ["eggs", "flour", "water"] }];
let ingredients_list_copy = ingredients_list;

で言うと、2 行目は「代入」=「コピー」であって、ingredients_list が指すのは参照型なので、上の「代入でオブジェクトをコピーしないのでシャローコピーという言葉を使わない」という考えなのですか?

0Like

質問者さんは「代入」=「コピー」もしくは「代入」の過程でコピーがあるという考えで、それがシャローコピーになるのかディープコピーになるのかという話がしたいのでしょうか?

いいえ、違います。

  • 参照型の場合、代入の過程では参照が代入されるだけでシャローコピーもディープコピーも行われない
  • 参照の代入にシャローコピーという言葉使うのは間違っている

という主張が正しいかを確認したいです。

で言うと、2 行目は「代入」=「コピー」であって、ingredients_list が指すのは参照型なので、上の「代入でオブジェクトをコピーしないのでシャローコピーという言葉を使わない」という考えなのですか?

私としては参照型変数の代入にコピーという言葉を使いたくないです。

  • 参照型変数はオブジェクトへの参照を保持する
  • 別の変数に代入すると変数が保持している参照を代入(これをコピーというのが混乱の元)するだけで、オブジェクトはコピーせずに共有する
  • オブジェクトをコピーしないのでシャローコピーという言葉を使わない
  • シャローコピー、ディープコピーはオブジェクをコピーする(新たなオブジェクト生成を伴う)際に使う用語である

という考えです。

0Like

私としては参照型変数の代入にコピーという言葉を使いたくないです。

最初の質問の画像に「代入コピー」と書いてありました。だから、質問者さんは「代入」=「コピー」もしくは「代入」の過程でコピーがあるという考えではないかと思ったのですが・・・

0Like

最初の質問の画像に「代入コピー」と書いてありました。だから、質問者さんは「代入」=「コピー」もしくは「代入」の過程でコピーがあるという考えではないかと思ったのですが・・・

紛らわしくてすみません。
「代入コピー」は「参照だけを複製する」という表現もできて、意見の食い違いが起きる原因にもなっていますね。
「代入」だけに修正しておきます。

0Like

間違った説明に思える例として、IT用語辞典 e-words の説明を引用します。

シャローコピーとは、配列やオブジェクトなどのデータ構造を複製する際、参照のみをコピーして実体の複製は作らない方式。

  • オブジェクトを複製する (私的コメント:新しいオブジェクトを生成するんですよね?)
  • 参照のみをコピーする (私的コメント:プリミティブ値はコピーしないのですか?)

オブジェクト内に参照がある場合には参照をコピーするだけで参照先の実体は複製しない、と読めば正しいことが書いてあるように思えます。しかし、

JavaScriptで「var x={a:1};」というオブジェクトがある場合、変数と同じように「y=x;」のような代入文を実行すると、yにはxの参照が代入され、xと同じメモリ領域を指し示すようになる。xyは同じ実体を表すため、複製後に「y.a=2;」のような操作を行なうと、x.aも同じ値(2)に変更される。このような複製方法をシャローコピーという。

シャローコピーしたなら y.a=2; では x.a は変更されないので間違った説明に思えるのですが、いかがでしょうか?

var x={a:1};

var y = x;       // これはシャローコピーではなくオブジェクト共有と言いたい
var z = {...x};  // これはシャローコピー、新たなオブジェクトを作り内容を複製している

y.a=2;
console.log(x.a);  // 2が表示され影響している(オブジェクト共有なので)
console.log(z.a);  // 1が表示され影響していない(シャローコピーした別オブジェクトなので)

シャローコピーを説明するなら以下のようなコードにする必要があると思います。

var x={a:1,b:{c:2}};

var y = {...x};  // 別オブジェクトを生成してシャローコピー、深い層はコピーせず共有

y.a=3;
y.b.c=4;
console.log(x.a);    // 1が表示される、浅い層は別オブジェクトなので影響しない
console.log(x.b.c);  // 4が表示される、深い層はオブジェクト共有なので影響する
1Like

「参照型オブジェクトの代入」では新たなオブジェクトが作られることはありません。
「シャローコピー」「ディープコピー」では同じ内容の新しいオブジェクトが必ず作成されます。シャロー/ディープの違いは「同じ内容」にするための操作に関するもので、新たなオブジェクトが作られることに変わりはないです。
こう書けばまったく違うことがわかりやすいのではないでしょうか。

2Like

PHPの配列は値型なので、代入で値がコピーされますね。
配列の中に参照型オブジェクトを入れてみると、シャローコピーであることがわかります。

<?php
$a = [0, [1], new stdClass()];
$a[2]->val = 2;
$a[2]->arr = [3];
$b = $a;            # PHPの配列は値型なので値や参照がコピーされる
$b[0] = 4;          # コピーした値を変更しているので$aには影響しない
$b[1][0] = 5;       # 配列の中の配列も値型でコピーされるので影響しない
$b[2]->val = 6;     # 配列の中のオブジェクトはコピーせず共有するので影響する
$b[2]->arr[0] = 7;  # 共有しているオブジェクトの中の配列なので影響する
print_r($a);        # $bへの代入で$aに影響しているか確認
実行結果
Array
(
    [0] => 0
    [1] => Array
        (
            [0] => 1
        )

    [2] => stdClass Object
        (
            [val] => 6
            [arr] => Array
                (
                    [0] => 7
                )

        )

)
1Like

シャロー/ディープの違いは「同じ内容」にするための操作に関するもので、新たなオブジェクトが作られることに変わりはないです。

参照型では、新たなオブジェクトを作るための操作が必要ですよね。
新たなオブジェクトを作ったあとにそのオブジェクトへの参照を変数に保持するのであって、代入によってオブジェクトがコピーされるわけではない、と。

JavaScript
var x={a:1,b:{c:2}};
var y={d:3,e:{f:4}};
var z={...x, ...y};  // JavaScriptのスプレッド構文ではシャローコピーされます
1Like

javascript に於いては 参照値が一致するのでシャローコピーではないですね。

値型に於いて だけ言うなら オブジェクトのコピーではない(=フィールドを持たない為)ので別と扱うべきです。 値型を定義できる C# だと 挙動としては シャローコピー(すべてのフィールド値の複製にあたる為)ですが。

0Like

Your answer might help someone💌