Je ne sais pas très bien dans quel cas je voudrais utiliser un récepteur de valeur au lieu d'utiliser toujours un récepteur de pointeur.
Pour récapituler à partir des documents:
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
La documentation dit également "Pour les types tels que les types de base, les tranches et les petites structures, un récepteur de valeur est très bon marché, donc à moins que la sémantique de la méthode ne l'exige un pointeur, un récepteur de valeur est efficace et clair. "
Premier point il dit que c'est "très bon marché", mais la question est plus c'est moins cher que le récepteur de pointeur. J'ai donc fait un petit benchmark (code sur Gist) qui m'a montré que le récepteur de pointeur est plus rapide même pour une structure qui n'a qu'un seul champ de chaîne. Voici les résultats:
// 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
(Edit: Veuillez noter que le deuxième point est devenu invalide dans les nouvelles versions go, voir les commentaires).
Deuxième point dit-il, c'est "efficace et clair" qui est plus une question de goût, n'est-ce pas? Personnellement, je préfère la cohérence en utilisant partout de la même manière. L'efficacité dans quel sens? en termes de performances, il semble que les pointeurs soient presque toujours plus efficaces. Peu d'essais avec une propriété int ont montré un avantage minimal du récepteur Value (plage de 0,01 à 0,1 ns/op)
Quelqu'un peut-il me dire un cas où un récepteur de valeur a clairement plus de sens qu'un récepteur de pointeur? Ou est-ce que je fais quelque chose de mal dans l'indice de référence, ai-je négligé d'autres facteurs?
Notez que le FAQ mentionne la cohérence
Vient ensuite la cohérence. Si certaines des méthodes du type doivent avoir des récepteurs de pointeur, les autres doivent également l'être, de sorte que l'ensemble de méthodes est cohérent quelle que soit la façon dont le type est utilisé. Voir section sur l'ensemble de méthodes s pour plus de détails.
Comme mentionné dans ce fil :
La règle concernant les pointeurs par rapport aux valeurs pour les récepteurs est que les méthodes de valeur peuvent être appelées sur des pointeurs et des valeurs, mais les méthodes de pointeur ne peuvent être invoquées que sur des pointeurs
À présent:
Quelqu'un peut-il me dire un cas où un récepteur de valeur a clairement plus de sens qu'un récepteur de pointeur?
Le commentaire de révision de code peut aider:
- Si le récepteur est une carte, func ou chan, n'utilisez pas de pointeur dessus.
- Si le récepteur est une tranche et que la méthode ne redimensionne pas ou ne réalloue pas la tranche, n'utilisez pas de pointeur dessus.
- Si la méthode doit muter le récepteur, le récepteur doit être un pointeur.
- Si le récepteur est une structure qui contient un
sync.Mutex
Ou un champ de synchronisation similaire, le récepteur doit être un pointeur pour éviter la copie.- Si le récepteur est une grande structure ou un tableau, un récepteur de pointeur est plus efficace. Quelle est la taille du grand? Supposons que cela équivaut à passer tous ses éléments en arguments à la méthode. Si cela semble trop grand, c'est aussi trop grand pour le récepteur.
- La fonction ou les méthodes, soit simultanément soit lorsqu'elles sont appelées à partir de cette méthode, peuvent-elles muter le récepteur? Un type de valeur crée une copie du récepteur lorsque la méthode est invoquée, donc les mises à jour externes ne seront pas appliquées à ce récepteur. Si des modifications doivent être visibles dans le récepteur d'origine, le récepteur doit être un pointeur.
- Si le récepteur est une structure, un tableau ou une tranche et que l'un de ses éléments est un pointeur vers quelque chose qui pourrait muter, préférez un récepteur de pointeur, car cela rendra l'intention plus claire pour le lecteur.
- Si le récepteur est un petit tableau ou une structure qui est naturellement un type valeur (par exemple, quelque chose comme le type
time.Time
), Avec pas de champs mutables et pas de pointeurs, ou est juste un simple type de base tel que int ou chaîne, un récepteur de valeur a du sens .
Un récepteur de valeur peut réduire la quantité de déchets qui peuvent être générés; si une valeur est transmise à une méthode value, une copie sur pile peut être utilisée au lieu d'allouer sur le tas. (Le compilateur essaie d'être intelligent pour éviter cette allocation, mais il peut ' t toujours réussir.) Ne choisissez pas un type de récepteur de valeur pour cette raison sans profiler d'abord.- Enfin, en cas de doute, utilisez un récepteur de pointeur.
La partie en gras se trouve par exemple dans 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) {
...
}
Pour ajouter en plus à @VonC une excellente réponse informative.
Je suis surpris que personne n'ait vraiment mentionné les coûts de maintenance une fois que le projet s'agrandit, que les anciens développeurs partent et que le nouveau arrive. Aller est sûrement une langue jeune.
D'une manière générale, j'essaie d'éviter les pointeurs quand je le peux mais ils ont leur place et leur beauté.
J'utilise des pointeurs lorsque:
Par exemple:
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
}
Raisons pour lesquelles j'évite les pointeurs:
Ma règle d'or, écrivez autant de méthodes encapsulées que possible telles que:
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)
MISE À JOUR:
Cette question m'a inspiré à rechercher plus sur le sujet et à écrire un blog à ce sujet https://medium.com/gophersland/Gopher-vs-object-oriented-golang-4fa62b88c701