1. ホーム
  2. go

[解決済み】golangでデータ構造をディープコピーする

2022-01-27 09:35:18

質問内容

あるデータ構造のインスタンスを複製したい。goにはビルトインがないので、サードパーティのライブラリを使っています。 https://github.com/emirpasic/gods .

例えば、ハッシュセットでディープコピーしてみたり。

var c, d hashset.Set
c = *hashset.New()
c.Add(1)
deepcopy.Copy(d, c)
c.Add(2)
fmt.Println(c.Contains(2))
fmt.Println(d.Contains(2))
fmt.Println(c.Contains(1))
fmt.Println(d.Contains(1))

しかし、ハッシュセットの中身は全くコピーされません。ディープコピーモジュールがエクスポートされていない値をコピーできないことは知っていますが、ライブラリには組み込みの "copy コンストラクタ" がないので、ライブラリでデータ構造のインスタンスをコードを修正せずに完全に複製することはできないということでしょうか?(同様の問題は、私が調べた他のいくつかのライブラリでも発生します)。

私はgolangの初心者ですが、同じようなことは例えばC++で簡単に実現できるため、正しいとは思えません。私は自分自身のバージョンを書いたり、彼らのコードを修正することができることを知っていますが、それは予想よりもあまりにも多くの仕事であり、それが慣用的な方法があるべきと考える理由です。

PS: 「そんな機能は必要ない」と言う人のために説明すると、私はいくつかのデータ構造を持つ複雑な状態を並列計算スレッドに分散しており、それらは状態を直接使用するので互いに干渉してはいけません。

どのように解決するのですか?

残念ながら、Goでこれを行う方法はありません。最初に思い浮かぶツールはリフレクションです(パッケージ reflect しかし、リフレクションを使うと、エクスポートされていないフィールドを読むことはできても、それを設定することはできないのです。参照 未エクスポートのフィールドを持つ構造体をクローンする方法は?

エクスポートされていないフィールドを持つ構造体をクローンする唯一の方法は、パッケージの unsafe (例はこちらをご覧ください。 golang/reflect の unexported フィールドにアクセスしますか? ) ですが、その名の通り、これは 安全でない なるべく使わないでください。を使用して作成されたプログラムは unsafe は、新しい Go リリースで動作し続ける保証はありませんし、どのプラットフォームでも同じ動作をする保証はありません。

一般的に Go でクローンをサポートする唯一かつ適切な方法は、パッケージ自体がそのような操作をサポートしている場合です。

注意点その1。

このことは、一部の 具体的 の場合、新しい値を作成し、その状態を手動で構築することで、クローンを模倣することができません。例えば map を作成し、元のマップのキーと値のペアを繰り返し、新しいマップに設定することです。

注意点その2。

未エクスポートのフィールドを持つ構造体の正確なコピーを作成するには、次のようにします。 代入 を別の(同じ型の)構造体変数にコピーすると、エクスポートされないフィールドも適切にコピーされます。

この例のように。

type person struct {
    Name string
    age  *int
}

age := 22
p := &person{"Bob", &age}
fmt.Println(p)

p2 := new(person)
*p2 = *p
fmt.Println(p2)

と出力されます(試しに 囲碁プレイグラウンド ):

&{Bob 0x414020}
&{Bob 0x414020}

を使って一般化することもできます。 reflect 具体的な型に依存することなく

type person struct {
    Name string
    age  *int
}

age := 22
p := &person{"Bob", &age}
fmt.Println(p)

v := reflect.ValueOf(p).Elem()
vp2 := reflect.New(v.Type())
vp2.Elem().Set(v)
fmt.Println(vp2)

これを 囲碁の遊び場 .

しかし、できないこと を変更することです。 person.age unexportedフィールドが他のものを指すようにします。宣言するパッケージの助けがなければ、これは nil または同じポインタ値(元のフィールドと同じオブジェクトを指す)。

関連もご覧ください。 golangでオブジェクトをディープコピーする素早い方法