В Go есть различные способы вернуть struct
значение или его часть. Для отдельных я видел:
type MyStruct struct {
Val int
}
func myfunc() MyStruct {
return MyStruct{Val: 1}
}
func myfunc() *MyStruct {
return &MyStruct{}
}
func myfunc(s *MyStruct) {
s.Val = 1
}
Я понимаю разницу между ними. Первый возвращает копию структуры, второй - указатель на значение структуры, созданное в функции, третий ожидает, что существующая структура будет передана, и переопределяет значение.
Я видел, как все эти паттерны использовались в разных контекстах, мне интересно, каковы лучшие практики в этом отношении. Когда бы вы использовали какой? Например, первый может подойти для небольших структур (поскольку накладные расходы минимальны), второй для больших. И третье, если вы хотите максимально эффективно использовать память, потому что вы можете легко повторно использовать один экземпляр структуры между вызовами. Есть ли лучшие практики, когда использовать какие?
Аналогично, тот же вопрос относительно ломтиков:
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} }
}
Опять же: каковы лучшие практики здесь. Я знаю, что срезы - это всегда указатели, поэтому возвращать указатель на срез не нужно. Однако должен ли я возвращать фрагмент структурных значений, фрагмент указателей на структуры, должен ли я указывать указатель на фрагмент в качестве аргумента (шаблон, используемый в API Go App Engine )?
new(MyStruct)
:) Но на самом деле нет никакой разницы между различными методами распределения указателей и их возврата.Ответы:
тл; др :
Один случай, когда вы должны часто использовать указатель:
Некоторые ситуации, когда вам не нужны указатели:
Рекомендации по проверке кода предлагают передавать в качестве значений небольшие структуры, например
type Point struct { latitude, longitude float64 }
, и, возможно, даже немного больше, если только вызываемая функция не должна иметь возможность изменять их на месте.bytes.Replace
занимает 10 слов аргументов (три среза и anint
).Для слайсов вам не нужно передавать указатель для изменения элементов массива.
io.Reader.Read(p []byte)
изменяет байтыp
, например. Возможно, это особый случай «обработки небольших структур как значений», поскольку внутренне вы передаете небольшую структуру, называемую заголовком среза (см. Объяснение Russ Cox (rsc) ). Точно так же вам не нужен указатель, чтобы изменить карту или общаться на канале .Для срезов вы изменяете (изменяете начало / длину / емкость) встроенные функции, такие как
append
принятие значения среза и возвращение нового. Я подражаю этому; избегая наложения псевдонимов, возврат нового среза помогает привлечь внимание к тому факту, что может быть выделен новый массив, и он знаком вызывающим.interface{}
параметре.Карты, каналы, строки, а также значения функций и интерфейсов , например срезы, являются внутренними ссылками или структурами, которые уже содержат ссылки, поэтому, если вы просто пытаетесь избежать копирования базовых данных, вам не нужно передавать на них указатели , (RSC написал отдельный пост о том, как хранятся значения интерфейса ).
flag.StringVar
принимает*string
по этой причине.Где вы используете указатели:
Подумайте, должна ли ваша функция быть методом с той структурой, на которую вам нужен указатель. Люди ожидают множество методов
x
для модификацииx
, поэтому создание измененной структуры получателя может помочь минимизировать удивление. Существуют рекомендации о том, когда приемники должны быть указателями.Функции, которые влияют на их параметры не получателя, должны прояснить это в godoc или, еще лучше, в godoc и в названии (например
reader.WriteTo(writer)
).Вы упоминаете принятие указателя, чтобы избежать выделения, разрешая повторное использование; изменение API для повторного использования памяти - это оптимизация, которую я буду откладывать до тех пор, пока не станет ясно, что выделения имеют нетривиальную стоимость, а затем я бы искал способ, который не навязывает более хитрый API всем пользователям:
bytes.Buffer
.Reset()
метод возврата объекта в пустое состояние, как предлагают некоторые типы stdlib. Пользователи, которым все равно или они не могут сохранить выделение, не должны вызывать его.existingUser.LoadFromJSON(json []byte) error
их можно обернутьNewUserFromJSON(json []byte) (*User, error)
. Опять же, это подталкивает выбор между ленью и ограничением распределения для каждого звонящего.sync.Pool
некоторые детали. Если конкретное распределение создает большое давление памяти, вы уверены, что знаете, когда выделение больше не используется, и у вас нет лучшей оптимизации,sync.Pool
может помочь. (CloudFlare опубликовал полезную (предварительнуюsync.Pool
) запись в блоге об утилизации.)И, наконец, о том, должны ли ваши срезы иметь указатели: срезы значений могут быть полезны и сэкономить ваши выделения и потери в кеше. Там могут быть блокировщики:
NewFoo() *Foo
а не отпускать Go, инициализировать нулевым значением .append
копирует элементы при увеличении базового массива . Указатели, которые вы получили до того, какappend
указали на неправильное место после, копирование может быть медленнее для огромных структур, например,sync.Mutex
копирование запрещено Вставка / удаление в середине и сортировка аналогичным образом перемещают элементы.В общих чертах, значения срезов могут иметь смысл, если вы либо располагаете все свои элементы заранее и не перемещаете их (например, не больше
append
s после начальной настройки), либо если вы продолжаете перемещать их, но вы уверены, что это ОК (нет / осторожного использования указателей на элементы, элементы достаточно малы для эффективного копирования и т. Д.). Иногда вам нужно подумать или измерить специфику вашей ситуации, но это грубое руководство.источник
Replace(s, old, new []byte, n int) []byte
; s, old и new - это три слова каждое ( заголовки слайса(ptr, len, cap)
) иn int
одно слово, поэтому 10 слов, что при восьми байтах на слово составляет 80 байтов.Три основные причины, по которым вы хотите использовать приемники методов в качестве указателей:
«Во-первых, и это наиболее важно, должен ли метод модифицировать приемник? Если это так, получатель должен быть указателем».
«Во-вторых, это вопрос эффективности. Если приемник большой, например, большая структура, будет гораздо дешевле использовать указатель приемника».
«Далее следует согласованность. Если у некоторых методов типа должны быть указатели-получатели, то и у остальных тоже должно быть, поэтому набор методов согласован независимо от того, как используется тип»
Ссылка: https://golang.org/doc/faq#methods_on_values_or_pointers
Изменить: Другая важная вещь, чтобы знать фактический «тип», который вы отправляете в функцию. Типом может быть «тип значения» или «ссылочный тип».
Даже если срезы и карты действуют как ссылки, нам может потребоваться передать их как указатели в сценариях, таких как изменение длины среза в функции.
источник
Случай, когда вам, как правило, нужно возвращать указатель, - это когда создается экземпляр некоторого ресурса или ресурса общего доступа . Это часто делается функциями с префиксом
New
.Поскольку они представляют конкретный экземпляр чего-либо и им может потребоваться координировать какую-либо деятельность, не имеет большого смысла генерировать дублированные / копируемые структуры, представляющие один и тот же ресурс - поэтому возвращаемый указатель действует как дескриптор самого ресурса ,
Некоторые примеры:
func NewTLSServer(handler http.Handler) *Server
- создать экземпляр веб-сервера для тестированияfunc Open(name string) (*File, error)
- вернуть дескриптор доступа к файлуВ других случаях указатели возвращаются только потому, что структура может быть слишком большой для копирования по умолчанию:
func NewRGBA(r Rectangle) *RGBA
- выделить изображение в памятиВ качестве альтернативы, непосредственного возврата указателей можно избежать, вместо этого возвращая копию структуры, которая содержит указатель внутри, но, возможно, это не считается идиоматическим:
источник
Если вы можете (например, не общий ресурс, который не нужно передавать в качестве ссылки), используйте значение. По следующим причинам:
Причина 1 : вы будете размещать меньше предметов в стеке. Выделение / освобождение из стека происходит немедленно, но выделение / освобождение в куче может быть очень дорогим (время выделения + сборка мусора). Вы можете увидеть некоторые основные цифры здесь: http://www.macias.info/entry/201802102230_go_values_vs_references.md
Причина 2 : особенно если вы храните возвращаемые значения в срезах, ваши объекты памяти будут более компактными в памяти: циклическое выполнение среза, в котором все элементы являются смежными, намного быстрее, чем итерирование среза, в котором все элементы являются указателями на другие части памяти , Не для шага косвенности, а для увеличения промахов кеша.
Разрушитель мифов : типичная строка кэша x86 занимает 64 байта. Большинство структур меньше, чем это. Время копирования строки кэша в памяти аналогично копированию указателя.
Только если критическая часть вашего кода работает медленно, я бы попробовал некоторую микрооптимизацию и проверил, улучшает ли использование указателей скорость, за счет меньшей читаемости и удобства использования.
источник