Ответ 1
тл; др:
- Методы, использующие указатели приемника, распространены; Основное правило для получателей:"Если есть сомнения, используйте указатель".
- Срезы, карты, каналы, строки, значения функций и значения интерфейса реализованы с помощью указателей внутри, и указатель на них часто избыточен.
- В других местах используйте указатели для больших структур или структур, которые вам придется изменить, и в противном случае передайте значения, потому что изменение объекта неожиданно с помощью указателя сбивает с толку.
Один случай, когда вы должны часто использовать указатель:
- Получатели являются указателями чаще, чем другие аргументы. Методы нередко изменяют то, к чему они обращаются, или когда именованные типы являются большими структурами, поэтому по умолчанию указывается указателям, за исключением редких случаев.
- Инструмент copyfighter Джеффа Ходжеса автоматически ищет не крошечные получатели, переданные по значению.
- Инструмент copyfighter Джеффа Ходжеса автоматически ищет не крошечные получатели, переданные по значению.
Некоторые ситуации, когда вам не нужны указатели:
Рекомендации по проверке кода предполагают передачу небольших структур, таких как
type Point struct { latitude, longitude float64 }
, и, возможно, даже несколько больших значений в виде значений, если только вызываемая функция не должна иметь возможность изменять их на месте.- Семантика значений позволяет избежать наложения псевдонимов, когда присваивание здесь изменяет значение неожиданно.
- Go-y не жертвует чистой семантикой ради небольшой скорости, и иногда передача небольших структур по значению на самом деле более эффективна, поскольку позволяет избежать пропусков кэша или выделения кучи.
- Итак, страница комментариев к обзору кода предлагает переходить по значению, когда структуры малы и, вероятно, останутся такими.
- Если "большая" отсечка кажется расплывчатой, это так; возможно, многие структуры находятся в диапазоне, где указатель или значение в порядке. В качестве нижней границы комментарии к обзору кода предполагают, что срезы (три машинных слова) целесообразно использовать в качестве получателей значений. Как что-то ближе к верхней границе,
bytes.Replace
берет аргументы на 10 слов (три среза иint
).
Для срезов вам не нужно передавать указатель для изменения элементов массива.
io.Reader.Read(p []byte)
изменяет байтыp
, например. Возможно, это особый случай "обработки небольших структур как значений", поскольку внутренне вы передаете небольшую структуру, называемую заголовком среза (см. объяснение Russ Cox (rsc)). Точно так же вам не нужен указатель для изменения карты или общения по каналу.Для срезов вы измените срез (измените начало/длину/емкость), встроенные функции, такие как
append
, принимают значение среза и возвращают новое. Я подражаю этому; избегая наложения псевдонимов, возвращение нового среза помогает привлечь внимание к тому факту, что новый массив может быть выделен и знаком для вызывающих.- Не всегда практично следовать этой схеме. Некоторые инструменты, такие как интерфейсы базы данных или сериализаторы, должны добавлять к фрагменту, тип которого неизвестен во время компиляции. Иногда они принимают указатель на фрагмент в параметре
interface{}
.
- Не всегда практично следовать этой схеме. Некоторые инструменты, такие как интерфейсы базы данных или сериализаторы, должны добавлять к фрагменту, тип которого неизвестен во время компиляции. Иногда они принимают указатель на фрагмент в параметре
Карты, каналы, строки, а также значения функций и интерфейсов, такие как слайсы, являются внутренними ссылками или структурами, которые уже содержат ссылки, поэтому, если вы просто пытаетесь избежать копирования базовых данных, вам не нужно передавать указатели на них. (rsc написал отдельный пост о том, как хранятся значения интерфейса).
- Вам все еще может потребоваться передать указатели в более редком случае, когда вы хотите изменить структуру вызывающей стороны:
flag.StringVar
принимает*string
по этой причине, например.
- Вам все еще может потребоваться передать указатели в более редком случае, когда вы хотите изменить структуру вызывающей стороны:
Где вы используете указатели:
Подумайте, должна ли ваша функция быть методом с той структурой, на которую вам нужен указатель. Люди ожидают, что множество методов в
x
изменятx
, поэтому создание модифицированной структуры приемника может помочь минимизировать удивление. Есть рекомендации о том, когда получатели должны быть указателями.Функции, которые влияют на их параметры не получателя, должны прояснить это в godoc или, еще лучше, в godoc и в названии (например,
reader.WriteTo(writer)
).Вы упоминаете принятие указателя, чтобы избежать выделения, разрешая повторное использование; изменение API для повторного использования памяти - это оптимизация, которую я буду откладывать до тех пор, пока она не прояснит, что распределение имеет нетривиальную стоимость, а затем я бы искал способ, который не навязывает более хитрый API всем пользователям:
- Чтобы избежать выделения средств, Go escape анализ - ваш друг. Иногда вы можете помочь ему избежать выделения кучи, создав типы, которые можно инициализировать с помощью тривиального конструктора, простого литерала или полезного нулевого значения, например
bytes.Buffer
. - Рассмотрим метод
Reset()
, чтобы вернуть объект в пустое состояние, как предлагают некоторые типы stdlib. Пользователи, которым все равно или они не могут сохранить выделение, не должны вызывать его. - Для удобства рассмотрим написание методов изменения на месте и функций создания с нуля в качестве совпадающих пар:
existingUser.LoadFromJSON(json []byte) error
может быть заключен вNewUserFromJSON(json []byte) (*User, error)
. Опять же, это подталкивает выбор между ленью и ограничением выделения для отдельного абонента. - Вызывающие абоненты, пытающиеся утилизировать память, могут
sync.Pool
обрабатывать некоторые детали. Если конкретное выделение создает большое давление памяти, вы уверены, что знаете, когда выделение больше не используется, и у вас нет лучшей оптимизации,sync.Pool
может помочь. (CloudFlare опубликовал полезный (pre-sync.Pool
) пост в блоге об утилизации.)
- Чтобы избежать выделения средств, Go escape анализ - ваш друг. Иногда вы можете помочь ему избежать выделения кучи, создав типы, которые можно инициализировать с помощью тривиального конструктора, простого литерала или полезного нулевого значения, например
Наконец, о том, должны ли ваши фрагменты быть указателями: фрагменты значений могут быть полезны и сэкономить ваши выделения и потери в кеше. Там могут быть блокировщики:
- API для создания ваших предметов может навязывать вам указатели, например, вам нужно вызвать
NewFoo() *Foo
, а не отпустить инициализацию Go с нулевым значением. - Желаемое время жизни элементов может не совпадать. Весь кусок освобождается сразу; если 99% элементов больше не нужны, но у вас есть указатели на другие 1%, весь массив остается выделенным.
- Перемещение предметов по может вызвать проблемы. В частности,
append
копирует элементы, когда он увеличивает базовый массив. Указатели, которые вы получили доappend
, указывают на неправильное место после, копирование может быть медленнее для огромных структур и, например, дляsync.Mutex
копирование запрещено. Вставьте/удалите посередине и сортируйте аналогично, перемещайте элементы.
В целом, срезы значений могут иметь смысл, если вы либо располагаете все свои элементы на месте и не перемещаете их (например, не больше append
после начальной настройки), либо если вы продолжаете перемещать их, но вы убедитесь, что все в порядке (нет/осторожное использование указателей на элементы, элементы достаточно малы для эффективного копирования и т.д.). Иногда вам нужно подумать или измерить специфику вашей ситуации, но это грубое руководство.