LoginSignup
7
1

More than 3 years have passed since last update.

Go言語で構造体をコピーするときに気をつけること

Last updated at Posted at 2019-10-14

構造体のフィールドにマップがある場合の挙動

構造体のフィールドにマップがある場合、構造体s1を代入でs2にコピーしたときに同じポインタを参照してしまうことへの対処法。(ホントは別々の構造体として扱いたい場合)

Go言語のマップは参照型

Go言語で構造体をコピーするとき、マップは参照型なので代入するとポインタがコピーされてしまう。
マップs2.mpをいじると元のマップs1.mpの値も書き換わってしまします。
(同じものを見ているからそれはそう)

package main

import (
    "fmt"
)

type st struct {
    i  int
    s  string
    b  bool
    sl []int
    mp map[int]string
}

func main() {
    s1 := st{i: 1,
        s:  "Go",
        b:  true,
        sl: []int{1, 2, 3},
        mp: map[int]string{1: "Go", 2: "Java", 3: "C#"},
    }
    fmt.Println(s1)
    s2 := s1
    s2.i = 10
    s2.s = "Python"
    s2.b = false
    s2.sl = append(s2.sl, 10)
    s2.mp[4] = "Haskell"

    fmt.Println(s1)
    fmt.Println(s2)

}

playground

下記のようにs1.mp[4]にHaskellが入ってしまう。

{1 Go true [1 2 3] map[1:Go 2:Java 3:C#]}
{1 Go true [1 2 3] map[1:Go 2:Java 3:C# 4:Haskell]}
{10 Python false [1 2 3 10] map[1:Go 2:Java 3:C# 4:Haskell]}

解決策

別のポインタを確保して、loopで値を入れ直す。


package main

import (
    "fmt"
)

type st struct {
    i  int
    s  string
    b  bool
    sl []int
    mp map[int]string
}

func (ss st) copy() st {
    newS := ss
    newS.mp = make(map[int]string)
    for k, v := range ss.mp {
        newS.mp[k] = v
    }
    return newS
}

func main() {
    s1 := st{i: 1,
        s:  "Go",
        b:  true,
        sl: []int{1, 2, 3},
        mp: map[int]string{1: "Go", 2: "Java", 3: "C#"},
    }
    fmt.Println(s1)

    s2 := s1.copy()
    s2.i = 10
    s2.s = "Python"
    s2.b = false
    s2.sl = append(s2.sl, 10)
    s2.mp[4] = "Haskell"

    fmt.Println(s1)
    fmt.Println(s2)

}

playground

{1 Go true [1 2 3] map[1:Go 2:Java 3:C#]}
{1 Go true [1 2 3] map[1:Go 2:Java 3:C#]}
{10 Python false [1 2 3 10] map[1:Go 2:Java 3:C# 4:Haskell]}

なぜスライスでは発生しないのか?

調べてみるとスライスもマップと同じ参照型
なのになぜ同じように元の値が変わらないか。

この記事に答えがありました。

Go言語は、容量オーバしたスライスに対して新しくメモリ領域を確保してコピーします。
appendした際に容量オーバしたので、ar1とar2は、それぞれ別の領域を参照していることになります。

ということはスライスの容量確保によっては同じ現象が起きることになる。
のでスライスはcopyを使うか、空スライスにappendするのが無難。
どちらがベストプラクティスなのか分かっていないので誰かご教えて下さい!

公式wikiに書いてあると教えていただきました。

スライスも新しいスライスを作成してappendするのが一例だそうです!
ここまでのまとめplayground

7
1
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1