Wskaźniki a wartości w parametrach i wartości zwrotne

W Go istnieją różne sposoby zwracania wartości struct lub jej wycinka. Dla poszczególnych widziałem:

type MyStruct struct {
    Val int
}

func myfunc() MyStruct {
    return MyStruct{Val: 1}
}

func myfunc() *MyStruct {
    return &MyStruct{}
}

func myfunc(s *MyStruct) {
    s.Val = 1
}
Rozumiem różnice między nimi. Pierwszy zwraca kopię struktury, drugi wskaźnik do wartości struktury utworzonej w funkcji, trzeci oczekuje przekazania istniejącej struktury i nadpisuje wartość.

Widziałem, że wszystkie te wzorce są używane w różnych kontekstach, zastanawiam się, jakie są najlepsze praktyki w tym zakresie. Kiedy użyjesz którego? Na przykład, pierwsza z nich może być ok dla małych struktur (ponieważ narzut jest minimalny), druga dla większych. A trzeci, jeśli chcesz być wyjątkowo wydajny w pamięci, ponieważ możesz łatwo ponownie użyć pojedynczej instancji struct między wywołaniami. Czy istnieją jakieś najlepsze praktyki dotyczące tego, kiedy korzystać z których?

Podobnie, to samo pytanie dotyczące plasterków:

func myfunc() []MyStruct {
    return []MyStruct{ MyStruct{Val: 1} }
}

func myfunc() []*MyStruct {
    return []MyStruct{ &MyStruct{Val: 1} }
}

func myfunc(s *[]MyStruct) {
    *s = []MyStruct{ MyStruct{Val: 1} }
}

func myfunc(s *[]*MyStruct) {
    *s = []MyStruct{ &MyStruct{Val: 1} }
}

Jeszcze raz: jakie są tutaj najlepsze praktyki. Wiem, że plasterki są zawsze wskaźnikami, więc zwracanie wskaźnika do plasterek nie jest przydatny. Jednak, czy powinienem zwrócić kawałek wartości struktury, kawałek wskaźników do struktur, czy powinienem przekazać wskaźnik do kawałka jako argument (wzorzec używany w Go App Engine API)?

 214
Author: twotwotwo, 2014-05-08

2 answers

Tl; dr :

  • metody wykorzystujące wskaźniki odbiornika są powszechne; regułą dla odbiorników jest , "jeśli masz wątpliwości, użyj wskaźnika."
  • plasterki, mapy, kanały, ciągi znaków, wartości funkcji i wartości interfejsu są zaimplementowane ze wskaźnikami wewnętrznymi, a wskaźnik do nich jest często zbędny.
  • gdzie indziej użyj wskaźników dla dużych struktur lub struktur, które będziesz musiał zmienić, a w przeciwnym razie podaj wartości, ponieważ zmienianie rzeczy przez zaskoczenie za pomocą wskaźnika jest mylące.

Jeden przypadek, w którym powinieneś często używać wskaźnika:

  • Odbiorniki są wskaźnikami częściej niż inne argumenty. Nie jest niczym niezwykłym, że metody modyfikują rzeczy, na których są wywoływane, lub że nazwane typy mają być dużymi strukturami, więc wytyczne są domyślnie , z wyjątkiem rzadkich przypadków.
    • Jeff Hodges' copyfighter narzędzie automatycznie wyszukuje nie-małe odbiorniki przekazane według wartości.

Niektóre sytuacje, w których nie potrzebujesz wskaźników:

  • Code review guidelines suggest passing small structs like type Point struct { latitude, longitude float64 }, and maybe even things a bit bigger, as values, unless the function you ' re called need to be able to modify them in place.

    • semantyka wartości pozwala uniknąć sytuacji aliasingu, gdy przypisanie tutaj zmienia wartość tam przez zaskoczenie.
    • it ' s not Go-y to sacrifice czysta semantyka dla niewielkiej prędkości, a czasami przekazywanie małych struktur przez wartość jest bardziej wydajne, ponieważ unika błędów pamięci podręcznej lub alokacji sterty.
    • tak więc Strona Go Wiki code review comments sugeruje przekazywanie wartości, gdy struktury są małe i prawdopodobnie tak pozostaną.
    • jeśli" duży " odciąg wydaje się niejasny, jest; prawdopodobnie wiele struktur znajduje się w zakresie, w którym wskaźnik lub wartość są w porządku. Jako dolna granica, komentarze do recenzji kodu sugerują plasterki (trzy słowa maszynowe) są rozsądne do wykorzystania jako odbiorniki wartości. Jako coś bliższego górnej granicy, bytes.Replace zajmuje 10 słów " wartości args (trzy plasterki i int).
  • Dla plasterków , nie trzeba podawać wskaźnika, aby zmienić elementy tablicy. io.Reader.Read(p []byte) zmienia na przykład bajty p. Jest to prawdopodobnie szczególny przypadek "traktuj małe struktury jak wartości", ponieważ wewnętrznie przekazujesz małą strukturę zwaną plasterkiem nagłówek (zobacz Wyjaśnienie Russa Coxa (RSC) ). Podobnie, nie potrzebujesz wskaźnika, aby zmodyfikować mapę lub komunikować się na kanale .

  • Dla plasterków będziesz reslic (zmienić początek/długość/pojemność), wbudowane funkcje, takie jak append akceptują wartość plasterka i zwracają nową. Imitowałbym to; unika aliasingu, zwrócenie nowego plasterka pomaga zwrócić uwagę na fakt, że nowa tablica może zostać przydzielona, i jest to znane rozmówcom.

      Nie zawsze jest to praktyczne. Niektóre narzędzia, takie jak interfejsy bazy danych lub serializery muszą dołączać do plasterka, którego Typ nie jest znany podczas kompilacji. Czasami akceptują wskaźnik do wycinka w parametrze interface{}.
  • Mapy, kanały, ciągi znaków oraz wartości funkcji i interfejsu, podobnie jak plasterki, są wewnętrznymi odniesieniami lub strukturami, które już zawierają odniesienia, więc jeśli próbujesz unikaj kopiowania podstawowych danych, nie musisz przekazywać im wskaźników. (rsc napisał osobny post na temat przechowywania wartości interfejsu ).

    • nadal może być konieczne podanie wskaźników w rzadszym przypadku, w którym chcesz zmodyfikować strukturę wywołującego: flag.StringVar bierze *string z tego powodu, na przykład.
  • Gdzie używasz wskaźników:

    • Zastanów się, czy twoja funkcja powinna być metodą na niezależnie od struktury, do której potrzebujesz wskaźnika. Ludzie oczekują wielu metod na x, aby zmodyfikować x, więc tworzenie zmodyfikowanej struktury odbiornika może pomóc zminimalizować zaskoczenie. Istnieją wytyczne kiedy odbiorniki powinny być wskaźnikami.

    • Funkcje, które mają wpływ na ich paramy niebędące odbiorcami, powinny to wyjaśnić w godoc, lub jeszcze lepiej, godoc i nazwa (jak reader.WriteTo(writer)).

    • Wspominasz, że akceptujesz wskaźnik, aby uniknąć alokacji przez pozwala na ponowne użycie; zmiana API ze względu na ponowne użycie pamięci jest optymalizacją, którą opóźniłbym, dopóki nie będzie jasne, że alokacje mają nietrywialny koszt, a następnie poszukałbym sposobu, który nie wymusza trudniejszego API na wszystkich użytkownikach: {]}

      1. aby uniknąć alokacji, Go analiza ucieczki jest twoim przyjacielem. Czasami możesz pomóc w uniknięciu alokacji sterty, tworząc typy, które można zainicjować za pomocą trywialnego konstruktora, zwykłego dosłownego lub użytecznej wartości zerowej, takiej jak bytes.Buffer.
      2. rozważ metodę Reset(), aby przywrócić obiekt do stanu pustego, jak oferują niektóre typy stdlib. Użytkownicy, którym nie zależy lub nie mogą zapisać przydziału, nie muszą go wywoływać.
      3. rozważ pisanie metod modify-in-place i tworzenie funkcji od podstaw jako pasujących par, dla wygody: existingUser.LoadFromJSON(json []byte) error Może być owinięte przez NewUserFromJSON(json []byte) (*User, error). Ponownie popycha wybór między lenistwem a szczypaniem alokacji do indywidualnego rozmówcy.
      4. rozmówcy chcący poddać recyklingowi pamięć może pozwolić sync.Pool zajmij się kilkoma szczegółami. Jeśli konkretny przydział tworzy dużą presję pamięci, jesteś pewien, że wiesz, kiedy alloc nie jest już używany i nie masz lepszej optymalizacji dostępnej, {16]} może pomóc. (CloudFlare opublikował przydatny (pre-sync.Pool) post na blogu o recyklingu.)
      5. Co ciekawe, dla skomplikowanych konstruktorów, {[19] } może czasami uniknąć alokacji, gdy NewFoo() by tego nie zrobił. Nie idiomatyczne; ostrożnie próbować tego w do domu.

    Wreszcie, czy plasterki powinny być wskaźnikami: plasterki wartości mogą być przydatne, i zapisać alokacje i chybienia pamięci podręcznej. Mogą występować blokery:

    • API do tworzenia twoich elementów może wymusić na Tobie wskaźniki, np. musisz wywołać NewFoo() *Foo, a nie puścić inicjalizację wartością zerową.
    • pożądane okresy życia przedmiotów mogą nie być takie same. Cały kawałek jest uwolniony od razu; jeśli 99% elementów nie jest już użyteczne, ale masz wskaźniki do pozostałych 1%, cała tablica pozostaje przydzielona.
    • przenoszenie przedmiotów wokół może spowodować problemy. W szczególności, append kopiuje elementy, gdy powiększa podstawową tablicę. Wskaźniki, które otrzymałeś przed append wskazują na złe miejsce po, kopiowanie może być wolniejsze dla dużych struktur, a Dla np. sync.Mutex kopiowanie nie jest dozwolone. Wstaw/Usuń w środku i sortowanie podobnie przenieś elementy w pobliżu.

    Ogólnie rzecz biorąc, plasterki wartości mogą mieć sens, jeśli albo masz wszystkie elementy na miejscu z przodu i nie przenieść je (np., nie więcej append s po wstępnej konfiguracji), lub jeśli nadal je przesuwać, ale jesteś pewien, że to jest w porządku (nie/ostrożne użycie wskaźników do elementów, elementy są wystarczająco małe, aby skutecznie kopiować, itp.). Czasami trzeba pomyśleć lub zmierzyć specyfikę swojej sytuacji, ale to jest szorstki przewodnik.

     277
    Author: twotwotwo,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
    2017-07-23 22:03:12

    Trzy główne powody, dla których chcesz używać odbiorników metody jako wskaźników:

    1. "po pierwsze i najważniejsze, czy metoda musi modyfikować odbiornik? Jeśli tak, odbiornik musi być wskaźnikiem."

    2. "po drugie, chodzi o efektywność. Jeśli odbiornik jest duży, na przykład duża struktura, znacznie tańsze będzie użycie odbiornika wskaźnika."

    3. "Następna jest konsekwencja. Jeżeli niektóre metody tego typu muszą mieć wskaźnik odbiorniki, reszta też powinna, więc zestaw metod jest spójny niezależnie od sposobu użycia typu "

    Numer referencyjny: https://golang.org/doc/faq#methods_on_values_or_pointers

    Edit: kolejną ważną rzeczą jest znać rzeczywisty "typ", który wysyłasz do funkcji. Typ może być "typem wartości" lub "typem odniesienia". Patrz rysunek poniżej:

    Tutaj wpisz opis obrazka

    Nawet jak plastry i mapy działają jako odniesienia, możemy chcieć aby przekazać je jako wskaźniki w scenariuszach takich jak zmiana długości plasterka w funkcji.

     2
    Author: Santosh Pillai,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
    2018-09-01 17:38:48