その場合、常にポインターレシーバーを使用するのではなく、バリューレシーバーを使用することは非常に不明確です。
ドキュメントの要約:
type T struct {
a int
}
func (tv T) Mv(a int) int { return 0 } // value receiver
func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver
docsは、「基本型、スライス、小さな構造体などの型の場合、値レシーバーは非常に安価であるため、メソッドのセマンティクスがポインター、値レシーバーを必要としない限り、効率的で明確です。」
最初のポイントそれは「非常に安い」と言っていますが、問題はポインター受信機よりも安いということです。そこで、小さなベンチマーク (Gistのコード) を作成しました。これは、ポインターレシーバーが文字列フィールドを1つだけ持つ構造体であっても高速であることを示しています。結果は次のとおりです。
// Struct one empty string property
BenchmarkChangePointerReceiver 2000000000 0.36 ns/op
BenchmarkChangeItValueReceiver 500000000 3.62 ns/op
// Struct one zero int property
BenchmarkChangePointerReceiver 2000000000 0.36 ns/op
BenchmarkChangeItValueReceiver 2000000000 0.36 ns/op
(編集:新しいバージョンでは2番目のポイントが無効になったことに注意してください。コメントを参照)。
2番目のポイント、それは「効率的で明確」だと言いますが、これはもっと好みの問題ですね。個人的には、どこでも同じように使用することで一貫性を好みます。どんな意味で効率ですか?パフォーマンスに関しては、ほとんどの場合、ポインターの方が効率的です。 1つのintプロパティを使用したテスト実行では、Valueレシーバーの最小の利点が示されました(範囲は0.01〜0.1 ns/op)
値受信者がポインター受信者より明らかに理にかなっているケースを誰かに教えてもらえますか?または、ベンチマークで何か間違ったことをしていますか、他の要因を見落としていましたか?
次は一貫性です。型のメソッドの一部にポインターレシーバーが必要な場合、残りにもポインターレシーバーが必要であるため、メソッドセットは、型の使用方法に関係なく一貫しています。詳細については、 メソッドセットのセクション sを参照してください。
前述のように このスレッドで :
レシーバーのポインターと値に関するルールは、ポインターと値に対して値メソッドを呼び出すことができますが、ポインターメソッドはポインターに対してのみ呼び出すことができるということです
今:
値受信者がポインター受信者より明らかに理にかなっているケースを誰かに教えてもらえますか?
コードレビューコメント が役立ちます。
- 受信者がマップ、func、またはchanの場合、それへのポインターを使用しないでください。
- レシーバーがスライスであり、メソッドがスライスの再スライスまたは再割り当てを行わない場合、そのポインターを使用しないでください。
- メソッドがレシーバーを変更する必要がある場合、レシーバーはポインターでなければなりません。
- 受信者が_
sync.Mutex
_または類似の同期フィールドを含む構造体である場合、受信者はコピーを避けるためにポインターでなければなりません。- レシーバーが大きな構造体または配列の場合、ポインターレシーバーの方が効率的です。どれくらい大きいですか?すべての要素を引数としてメソッドに渡すことと同等であると仮定します。それが大きすぎると感じた場合、受信機にとっても大きすぎます。
- 関数またはメソッドは、同時に、またはこのメソッドから呼び出されたときに、レシーバーを変更できますか?値型は、メソッドが呼び出されるときにレシーバーのコピーを作成するため、外部の更新はこのレシーバーに適用されません。元のレシーバーで変更を表示する必要がある場合、レシーバーはポインターでなければなりません。
- レシーバーが構造体、配列、またはスライスであり、その要素のいずれかが変化している可能性のあるものへのポインターである場合は、ポインターレシーバーを優先します。
- レシーバーが自然に値型である小さな配列または構造体の場合(たとえば、_
time.Time
_型のような)、可変フィールドおよびポインターなし、または単なる単純型intやstringなどの基本型、値の受信者が意味をなす。
値の受信者は、生成可能なガベージの量を減らすことができます。値がvalueメソッドに渡される場合、ヒープに割り当てる代わりに、スタック上のコピーを使用できます。(コンパイラは、この割り当てを回避することを賢くしようとしますが、常に成功するとは限りません。)最初にプロファイリングせずに、この理由で値の受信者タイプを選択しないでください。- 最後に、疑わしい場合は、ポインターレシーバーを使用します。
太字の部分は、たとえば net/http/server.go#Write()
にあります。
_// Write writes the headers described in h to w.
//
// This method has a value receiver, despite the somewhat large size
// of h, because it prevents an allocation. The escape analysis isn't
// smart enough to realize this function doesn't mutate h.
func (h extraHeader) Write(w *bufio.Writer) {
...
}
_
@VonCにさらに有益な回答を追加します。
プロジェクトが大きくなり、古い開発者が去り、新しい開発者が登場すると、メンテナンスコストについて誰も本当に言及しなかったことに驚いています。 Goは確かに若い言語です。
一般的に言えば、私はできる限りポインターを避けようとしますが、それらには場所と美しさがあります。
次の場合にポインタを使用します:
例えば:
type TokenCache struct {
cache map[string]map[string]bool
}
func (c *TokenCache) Add(contract string, token string, authorized bool) {
tokens := c.cache[contract]
if tokens == nil {
tokens = make(map[string]bool)
}
tokens[token] = authorized
c.cache[contract] = tokens
}
ポインタを避ける理由:
私の経験則では、次のようなカプセル化されたメソッドをできるだけ多く記述します。
package rsa
// EncryptPKCS1v15 encrypts the given message with RSA and the padding scheme from PKCS#1 v1.5.
func EncryptPKCS1v15(Rand io.Reader, pub *PublicKey, msg []byte) ([]byte, error) {
return []byte("secret text"), nil
}
cipherText, err := rsa.EncryptPKCS1v15(Rand, pub, keyBlock)
UPDATE:
この質問により、私はこのトピックをさらに調査し、それに関するブログ投稿を書くことになりました https://medium.com/gophersland/Gopher-vs-object-oriented-golang-4fa62b88c701