ある状態でコードにバグがあり、他の状態ではない理由を理解するのに苦労しています。ポインタをカバーしてから久しぶりなので、さびたかも!
基本的に、オブジェクトをメモリに格納するために使用しているリポジトリ構造があり、Store
関数があります。
type chartsRepository struct {
mtx sync.RWMutex
charts map[ChartName]*Chart
}
func (r *chartsRepository) Store(c *Chart) error {
r.mtx.Lock()
defer r.mtx.Unlock()
r.charts[c.Name] = c
return nil
}
したがって、RWミューテックスロックをオンにし、識別子によって参照されるマップへのポインターを追加するだけです。
次に、基本的にこれらのオブジェクトのスライスをループして、すべてをリポジトリに格納する関数を用意しました。
type service struct {
charts Repository
}
func (svc *service) StoreCharts(arr []Chart) error {
hasError := false
for _, chart := range arr {
err := svc.repo.Store(&chart)
// ... error handling
}
if hasError {
// ... Deals with the error object
return me
}
return nil
}
上記は機能しません。最初はすべて正常に機能しているように見えますが、後でデータにアクセスしようとすると、キーが異なるにもかかわらず、マップのエントリはすべて同じChart
オブジェクトを指しています。
次のようにしてポインター参照を別の関数に移動すると、すべてが期待どおりに機能します。
func (svc *service) StoreCharts(arr []Chart) error {
// ...
for _, chart := range arr {
err := svc.storeChart(chart)
}
// ...
}
func (svc *service) storeChart(c Chart) error {
return svc.charts.Store(&c)
}
この問題は、ループがchart
ループ内のfor
への参照を上書きするため、ポインターの参照も変更されることを想定しています。ポインターが独立した関数で生成されると、その参照は上書きされません。そうですか?
ばかげているように感じますが、&chart
によってポインタを生成するべきではありません。これはchart
参照とは無関係ですか?また、for
ループでポインターp := &chart
の新しい変数を作成しようとしましたが、それも機能しませんでした。
ループ内でポインタを生成することを避けるべきですか?
これは、単一のループ変数chart
があり、各反復で新しい値がそれに割り当てられるだけだからです。したがって、ループ変数のアドレスを取得しようとすると、各反復で同じになるため、同じポインターを格納し、指定されたオブジェクト(ループ変数)が各反復(およびループの後)で上書きされます。最後の反復で割り当てられた値を保持します)。
これは で説明されています。仕様:ステートメントの場合:range
句を含むステートメントの場合:
反復変数は、 short変数宣言 (
:=
)。この場合、それらのタイプはそれぞれの反復値のタイプに設定され、それらの scope は「for」ステートメントのブロックです。 これらは各反復で再利用されます。反復変数が「for」ステートメントの外側で宣言されている場合、実行後の値は最後の反復の値になります。
2番目のバージョンは機能します。ループ変数を関数に渡すため、そのコピーが作成され、コピーのアドレス(ループ変数から切り離されています)を格納します。
ただし、関数なしでも同じ効果を得ることができます。ローカルコピーを作成し、そのアドレスを使用するだけです。
for _, chart := range arr {
chart2 := chart
err := svc.repo.Store(&chart2) // Address of the local var
// ... error handling
}
また、スライス要素のアドレスも保存できることに注意してください。
for i := range arr {
err := svc.repo.Store(&arr[i]) // Address of the slice element
// ... error handling
}
これの欠点は、スライス要素へのポインターを格納するため、ポインターのいずれかを保持している限り、スライスのバッキング配列全体をメモリーに保持する必要があることです(配列はガベージコレクションできません)。さらに、保存したポインターはスライスと同じChart
値を共有するため、渡されたスライスのチャート値を誰かが変更した場合、保存したポインターを持つチャートに影響を与えます。
関連する質問を参照してください: