Es ist für mich sehr unklar, in welchem Fall ich einen Wertempfänger verwenden möchte, anstatt immer einen Zeigerempfänger zu verwenden.
Um es aus den Dokumenten zusammenzufassen:
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
Das docs sagt auch: "Für Typen wie Basistypen, Slices und kleine Strukturen ist ein Wertempfänger sehr billig, es sei denn, die Semantik der Methode erfordert dies ein Zeiger, ein Wertempfänger ist effizient und klar. "
Der erste Punkt besagt, dass es "sehr billig" ist, aber die Frage ist mehr, ob es billiger ist als der Zeigerempfänger. Also habe ich einen kleinen Benchmark erstellt (code on Gist) , der mir zeigte, dass der Zeigerempfänger auch für eine Struktur mit nur einem Zeichenfolgenfeld schneller ist. Das sind die Ergebnisse:
// 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
(Bearbeiten: Bitte beachten Sie, dass der zweite Punkt in neueren go-Versionen ungültig wurde, siehe Kommentare).
Der zweite Punkt besagt, dass es "effizient und klar" ist, was mehr eine Geschmackssache ist, nicht wahr? Persönlich bevorzuge ich Konsistenz, indem ich überall den gleichen Weg benutze. Effizienz in welchem Sinne? In Bezug auf die Leistung scheint es, dass Zeiger fast immer effizienter sind. Wenige Testläufe mit einer int-Eigenschaft zeigten einen minimalen Vorteil des Value-Empfängers (Bereich von 0,01 bis 0,1 ns/op).
Kann mir jemand einen Fall nennen, in dem ein Wertempfänger eindeutig sinnvoller ist als ein Zeigerempfänger? Oder mache ich im Benchmark etwas falsch, habe ich andere Faktoren übersehen?
Beachten Sie, dass in FAQ wird die Konsistenz erwähnt
Weiter ist Konsistenz. Wenn einige der Methoden des Typs Zeigerempfänger haben müssen, sollte dies auch der Rest sein, damit der Methodensatz unabhängig von der Verwendung des Typs konsistent ist. Einzelheiten finden Sie in Abschnitt über Methodensätze s.
Wie erwähnt in diesem Thread :
Die Regel für Zeiger im Vergleich zu Werten für Empfänger lautet, dass Wertemethoden für Zeiger und Werte aufgerufen werden können, Zeigermethoden jedoch nur für Zeiger
Jetzt:
Kann mir jemand einen Fall nennen, in dem ein Wertempfänger eindeutig sinnvoller ist als ein Zeigerempfänger?
Das Code Review Kommentar kann helfen:
- Wenn es sich bei dem Empfänger um eine Karte, eine Funk oder einen Kanal handelt, verwenden Sie keinen Zeiger darauf.
- Wenn der Empfänger ein Slice ist und die Methode das Slice nicht neu verschiebt oder zuordnet, verwenden Sie keinen Zeiger darauf.
- Wenn die Methode den Empfänger mutieren muss, muss der Empfänger ein Zeiger sein.
- Wenn der Empfänger eine Struktur ist, die ein
sync.Mutex
Oder ein ähnliches Synchronisierungsfeld enthält, muss der Empfänger ein Zeiger sein, um ein Kopieren zu vermeiden.- Wenn der Empfänger eine große Struktur oder ein großes Array ist, ist ein Zeigerempfänger effizienter. Wie groß ist groß? Angenommen, es entspricht der Übergabe aller Elemente als Argumente an die Methode. Wenn sich das zu groß anfühlt, ist es auch zu groß für den Empfänger.
- Können Funktionen oder Methoden gleichzeitig oder beim Aufruf dieser Methode den Empfänger mutieren? Ein Wertetyp erstellt beim Aufrufen der Methode eine Kopie des Empfängers, sodass keine externen Aktualisierungen auf diesen Empfänger angewendet werden. Wenn Änderungen im ursprünglichen Empfänger sichtbar sein müssen, muss der Empfänger ein Zeiger sein.
- Wenn der Empfänger eine Struktur, ein Array oder ein Slice ist und eines seiner Elemente ein Zeiger auf etwas ist, das möglicherweise mutiert, bevorzugen Sie einen Zeigerempfänger, da dies dem Leser die Absicht klarer macht.
- Wenn der Empfänger ein kleines Array oder eine kleine Struktur ist, bei der es sich natürlich um einen Werttyp handelt (zum Beispiel so etwas wie der Typ
time.Time
), Mit Keine veränderlichen Felder und keine Zeiger oder nur ein einfacher Basistyp wie int oder string. Ein Wertempfänger ist sinnvoll .
Ein Wertempfänger kann die Menge an Müll reduzieren, die erzeugt werden kann. Wenn ein Wert an eine value-Methode übergeben wird, kann eine On-Stack-Kopie verwendet werden, anstatt ihn auf dem Heap zuzuweisen. Dies ist nicht immer erfolgreich.) Wählen Sie aus diesem Grund keinen Wertempfängertyp, ohne zuvor ein Profil zu erstellen.- Verwenden Sie im Zweifelsfall einen Zeigerempfänger.
Der fettgedruckte Teil befindet sich beispielsweise in 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) {
...
}
Ergänzend zu @VonC tolle, aussagekräftige Antwort hinzufügen.
Ich bin überrascht, dass niemand die Wartungskosten wirklich erwähnte, sobald das Projekt größer wird, alte Entwickler gehen und neue kommen. Go ist sicher eine junge Sprache.
Im Allgemeinen versuche ich, Hinweise zu vermeiden, wenn ich kann, aber sie haben ihren Platz und ihre Schönheit.
Ich benutze Zeiger, wenn:
Z.B:
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
}
Gründe, warum ich Hinweise vermeide:
Meine Faustregel: Schreiben Sie so viele gekapselte Methoden wie möglich wie:
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:
Diese Frage hat mich dazu inspiriert, das Thema genauer zu untersuchen und einen Blog-Beitrag darüber zu schreiben https://medium.com/gophersland/Gopher-vs-object-oriented-golang-4fa62b88c701