MAGAZINE
ルーターマガジン
データ/フォーマット
データを扱う時に以外にミスする、深いコピーと浅いコピー
2018.07.20
はじめまして、ルーターのKIMです。
プログラミング、特にデータ系のプログラムを作るときはいろんな形の出会えます。 Rubyでは基本的に「文字列」や「数字」以外にも「配列(Array)」とか「ハッシュ(Hash)」などいろんな形式が存在します。 そういうデータを出力したり、どこかに保存したり、コピーしたりします。その中でコピーは簡単に見えても以外の複雑な機能です。
ですが、コピーを話す前にまずデータが保存される変数について説明する必要があります。 Rubyだけではなくほぼ全てのプログラミング言語は変数については同じ構造を持っています。 変数はデータである「値(value)」、よく変数名と呼ばれる「識別子(identifier)」、メモリにデータが保存されるところの「住所(address)」で作られています。 C言語とかでは変数の住所などをプログラマーが操作できるようになっていますが、そのためにはメモリの構造などを把握する必要があるとか不便なところが多いので、最近の言語ではその辺については自動的に処理してくれるようになっています。 そのおかげでプログラミングの効率が上がりましたが、逆に自動処理が問題となる時もあります。 その問題がよく起きるひとつの例といえるのがコピーです。
コピーはある変数が持っている値を他の変数に渡せて両方同じ値を持つ状態になることをいいます。 Rubyでは基本的に変数を宣言するときと同じ演算子(=)を使ってコピーができます。
例えば、こういう感じでコピーを使えます。
このコードを実行してみると、確かに同じ値を持っているように見えます。
そうしたらこの例はどうですかね
これも上の例のように同じ値を持っています。
では、このコードに1行追加してみます。
あら?確かにarr_1の内容を変更しましたが、なぜかarr_2の内容も変更されました。 その理由はイコール(=)演算子を使うと「浅いコピー」になるからです。 実はプログラミングでのコピーには「浅いコピー」と「深いコピー」二つがありまして、浅いコピーは変数の値を渡すことではなく住所を渡します。
この画像のようにarr_1とarr_2を同じところを参照していたため、arr_1を編集してもarr_2の内容まで変更されたようにみえているのです。 本当ですか?と思う方もいると思いますので、実際に二つの変数が同じ住所を持っているのかを確認してみましょう。
object_idというメソッドを使うと変数の住所をみられますので、確認してみると二つも同じ住所を持っているのを確認できます。
では、これのようなことを防止するためにはどうすればいいんですかね?ここで登場するのが「深いコピー」です。
「深いコピー」とは住所ではなく変数の値だけを渡して全く別の変数を作ることを言います。 Rubyでは「Marshal」というクラスで深いコピーを使えます。
これでやっと同じ値をもっていても完全に別の変数が生成されました。
浅いコピーと深いコピーの問題は特に大量のデータを扱う時に注意しないと大変なことになりますので、必ず注意しましょう!
以上ルーターのKIMでした。
プログラミング、特にデータ系のプログラムを作るときはいろんな形の出会えます。 Rubyでは基本的に「文字列」や「数字」以外にも「配列(Array)」とか「ハッシュ(Hash)」などいろんな形式が存在します。 そういうデータを出力したり、どこかに保存したり、コピーしたりします。その中でコピーは簡単に見えても以外の複雑な機能です。
ですが、コピーを話す前にまずデータが保存される変数について説明する必要があります。 Rubyだけではなくほぼ全てのプログラミング言語は変数については同じ構造を持っています。 変数はデータである「値(value)」、よく変数名と呼ばれる「識別子(identifier)」、メモリにデータが保存されるところの「住所(address)」で作られています。 C言語とかでは変数の住所などをプログラマーが操作できるようになっていますが、そのためにはメモリの構造などを把握する必要があるとか不便なところが多いので、最近の言語ではその辺については自動的に処理してくれるようになっています。 そのおかげでプログラミングの効率が上がりましたが、逆に自動処理が問題となる時もあります。 その問題がよく起きるひとつの例といえるのがコピーです。
コピーはある変数が持っている値を他の変数に渡せて両方同じ値を持つ状態になることをいいます。 Rubyでは基本的に変数を宣言するときと同じ演算子(=)を使ってコピーができます。
例えば、こういう感じでコピーを使えます。
num_1 = 5 num_2 = num_1 puts "num_1の内容は#{num_1}です。" puts "num_2の内容は#{num_2}です。"
このコードを実行してみると、確かに同じ値を持っているように見えます。
そうしたらこの例はどうですかね
arr_1 = [1, 2, 3] arr_2 = arr_1 puts arr_1 puts arr_2
これも上の例のように同じ値を持っています。
では、このコードに1行追加してみます。
arr_1 = [1, 2, 3] arr_2 = arr_1 arr_1[0] = 4 puts arr_1 puts arr_2
あら?確かにarr_1の内容を変更しましたが、なぜかarr_2の内容も変更されました。 その理由はイコール(=)演算子を使うと「浅いコピー」になるからです。 実はプログラミングでのコピーには「浅いコピー」と「深いコピー」二つがありまして、浅いコピーは変数の値を渡すことではなく住所を渡します。
この画像のようにarr_1とarr_2を同じところを参照していたため、arr_1を編集してもarr_2の内容まで変更されたようにみえているのです。 本当ですか?と思う方もいると思いますので、実際に二つの変数が同じ住所を持っているのかを確認してみましょう。
arr_1 = [1, 2, 3] arr_2 = arr_1 puts arr_1.object_id puts arr_2.object_id
object_idというメソッドを使うと変数の住所をみられますので、確認してみると二つも同じ住所を持っているのを確認できます。
では、これのようなことを防止するためにはどうすればいいんですかね?ここで登場するのが「深いコピー」です。
「深いコピー」とは住所ではなく変数の値だけを渡して全く別の変数を作ることを言います。 Rubyでは「Marshal」というクラスで深いコピーを使えます。
arr_1 = [1, 2, 3] arr_2 = Marshal.load(Marshal.dump(arr_1)) puts arr_1, arr_1.object_id puts arr_2, arr_2.object_id
これでやっと同じ値をもっていても完全に別の変数が生成されました。
浅いコピーと深いコピーの問題は特に大量のデータを扱う時に注意しないと大変なことになりますので、必ず注意しましょう!
以上ルーターのKIMでした。
CONTACT
お問い合わせ・ご依頼はこちらから