ポインター レシーバー メソッドを使用したインスタンスのコピーの落とし穴
Go では、名前付き型 T のすべてのメソッドが T 自体のレシーバー型を持つ場合(*T ではない)、そのタイプのインスタンスのコピーは安全であると考えられます。これは、メソッド呼び出しは必然的にコピーに対して動作し、元の値が変更されないことが保証されるためです。ただし、T のメソッドにポインター レシーバーがある場合、T のインスタンスをコピーすることは危険である可能性があります。
説明
メソッドを呼び出すときに、そのメソッドが呼び出される値on が最初にコピーされ、そのコピーがレシーバーとして渡されます。型に値レシーバーを持つメソッドのみがある場合、メソッドのアクションに関係なく、メソッドが元の値を変更できないことが保証されます。これは、コピーが常に利用され、元の値が意図しない変更から保護されるためです。
ただし、型にポインター レシーバーを持つメソッドがある場合、それらのメソッドはコピーではなく元の指す値を変更する可能性があります。これは、メソッドが元の値へのポインタを受け取り、基になるデータを変更できるためです。
例
ラッパー型 Wrapper:
type Wrapper struct { v int p *int }
Set() メソッドを使用して、両方のフィールドに同じ値が含まれていることを確認します。
func (w *Wrapper) Set(v int) { w.v = v *w.p = v }
Wrapper のインスタンスを作成した場合:
a := Wrapper{v: 0, p: new(int)}
その後、 a のコピー (b):
b := a
Set():
a.Set(1)
を使用して a を 1 に設定した後、a と b の両方のフィールドが 1 に設定されることが期待されます。ただし、それらの値を出力すると、別の話が明らかになります。
fmt.Printf("a.v=%d, a.p=%d; b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)
出力:
a.v=1, a.p=1; b.v=0, b.p=1
この不一致の理由は、b のポインターがコピーされている間、依然として同じものを参照していることです。基になるデータを a のポインターとして使用します。 Set() がポイントされた値を変更すると、Wrapper の両方のコピーに影響します。ただし、非ポインター フィールド v は、a と b の間で区別されます。
ベスト プラクティス
この問題を回避するには、次のような型のインスタンスをコピーしないことをお勧めします。ポインタ受信メソッド。ポインター値の操作が必要な場合は、ポインター自体 (*T) をコピーすることが実行可能な代替手段です。
以上が## ポインター レシーバー メソッドを使用して Go 型のインスタンスをコピーするのはなぜ危険ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。