Разница в использовании указателя в структурах полей - PullRequest
0 голосов
/ 29 января 2020

Таким способом мы можем создавать структуры golang. Примеры ниже: Каковы различия между этими двумя?

// Usual way
type Employee struct {
    firstName string    `json:"name"`
    salary    int       `json:"salary"`
    fullTime  bool      `json:"fullTime"`
    projects  []Project `json:"projects"`
}

// Un-usal way with pointers
type Employee struct {
    firstName *string    `json:"name"`
    salary    *int       `json:"salary"`
    fullTime  *bool      `json:"fullTime"`
    projects  *[]Project `json:"projects"`
}

Есть ли какие-либо компромиссы, такие как память?

Обновление:

Предполагаемая ниже функция:

// this function consumes MORE memory
func printEmployeeWithoutPointer(employee Employee) {
    // print here
}

// this function consumes LESS memory
func printEmployeeWithPointer(employee *Employee) {
    // print here
}

Ответы [ 4 ]

3 голосов
/ 29 января 2020

Мы используем указатели для обмена данными , но это не всегда означает, что он более эффективен в памяти или более производительный. Go очень хорошо и быстро копирует данные.

Когда дело касается структур, общая причина использования указателей состоит в том, что указатели могут иметь значения nil, а примитивы - нет. Если вам нужна структура с полем опций, вы должны использовать указатели

Если вы десериализуете JSON, то вы можете опустить поля, используя omitempty. Здесь fullTime является необязательным

type Employee struct {
    firstName string `json:"name"`
    salary int `json:"salary"`
    fullTime *bool `json:"fullTime,omitempty"`
}

Производительность при использовании JSON

Если вы десериализуете JSON в указатели в надежде на сохранение памяти, вы выиграли ' т. С точки зрения JSON каждый элемент уникален, поэтому обмен данными отсутствует. Вы будете использовать больше памяти, потому что теперь каждое значение должно хранить значение и указатель на значение. И это будет медленнее, потому что вам нужно все время разыменовывать указатели

2 голосов
/ 29 января 2020

Да, есть несколько вещей, которые нужно учитывать. Сначала: давайте начнем с очевидной синтаксической ошибки в вашем примере указателя:

type Employee struct {
    FirstName *string `json:"name"`
    Salary    *int    `json:"salary"`
    FullTime  *bool   `json:"fullTime"`
}

Итак, я переместил звездочку в тип и заполнил поля. Пакет encoding/json использует отражение для установки значений полей, поэтому их необходимо экспортировать.

Поскольку вы используете теги json, давайте начнем с простых вещей:

type Foo struct {
    Bar string  `json:"bar"`
    Foo *string `json:"foo,omitempty"`
}

Когда я отменяю сообщение, которое не имеет значения bar, поле Bar будет просто пустой строкой. Из-за этого сложно понять, было ли поле отправлено или нет. Особенно при работе с целыми числами: как я могу отличить поле, которое не было отправлено, от поля, которое было отправлено со значением 0?
Использование поля указателя и указание omitempty позволяет вам это сделать , Если поле не было указано в данных JSON, то поле в вашей структуре будет nil, если нет: оно будет указывать на целое число со значением 0.

Конечно, имея проверка на наличие нулевых указателей может быть утомительной, это делает код более подверженным ошибкам, и поэтому вам нужно делать это только в том случае, если есть реальная причина, по которой вы хотите провести различие между полем, которое не устанавливается, и нулевым значением.


подводные камни

Указатели позволяют изменять значения того, на что они указывают

Перейдем к рискам, которые по своей природе связаны с ними. Предполагая, что ваша Employee структура с полями указателя, и тип с именем EmployeeV, который такой же, но с полями значений, рассмотрим следующие функции:

func (e Employee) SetName(name string) {
    if e.Firstname == nil {
        e.Firstname = &name
        return
    }
    *e.Firstname = name
}

Теперь эта функция будет работать только на половине время. Вы звоните SetName на получателя значения. Если Firstname равно nil, тогда вы установите указатель на копию вашей исходной переменной, и ваша переменная не будет отражать изменения, внесенные вами в функцию. Однако, если Firstname было установлено , копия будет указывать на ту же строку, что и исходная переменная, а значение, на которое указывает указатель на , будет обновляться. Это плохо.

Реализуйте ту же функцию на EmployeeV, однако:

func (e EmployeeV) SetName(name string) {
    e.Firstname = name
}

И она просто не будет когда-либо работать. Вы всегда будете обновлять копию, и эти изменения не затронут переменную, для которой вы вызываете функцию SetName. По этой причине идиоматический c способ, в go, сделать что-то вроде этого будет:

type Employee struct {
    Firstname string
    // other fields
}

func (e *Employee) SetName(name string) {
    e.Firstname = name
}

Итак, мы меняем метод для использования указателя receiveer.

Data races

Как всегда: если вы используете указатели, вы, по сути, позволяете коду манипулировать памятью, на которую указывает что-то напрямую. Учитывая, что golang является языком, который, как известно, облегчает параллелизм, и доступ к тому же биту памяти означает, что вы рискуете создать гонки данных:

func main() {
    n := "name"
    e := Employee{
        Firstname: &n,
    }
    go func() {
         *e.Firstname = "foo"
    }()
    race(e)
}

func race(e Employee) {
    go race(e)
    go func() {
        *e.Firstname = "in routine"
    }()
    *e.Firstname = fmt.Sprintf("%d", time.Now().UnixNano())
}

Доступ к этому полю Firstname во множестве разных процедур. Какова будет его возможная ценность? Ты вообще знаешь? Детектор расы golang, скорее всего, пометит этот код как потенциальную гонку данных.


С точки зрения использования памяти: отдельные поля, такие как целые или тип bools, на самом деле не должны беспокоить около. Если вы передаете размерную структуру и знаете, что она безопасна, то, вероятно, будет хорошей идеей передать указатель на указанную структуру. С другой стороны, доступ к значениям через указатель, а не прямой доступ к ним, не является бесплатным: косвенное добавление добавляет небольшие накладные расходы.

0 голосов
/ 01 февраля 2020

Я рекомендую прочитать это сообщение ссылка

0 голосов
/ 30 января 2020

FYI, дополнительное чтение https://github.com/golang/go/wiki/CodeReviewComments#pass -значения .

Передача значений Не передавайте указатели в качестве аргументов функции только для того, чтобы сохранить несколько байтов. Если функция ссылается на свой аргумент x только как * x, тогда аргумент не должен быть указателем. Распространенными примерами этого являются передача указателя на строку (* string) или указателя на значение интерфейса (* io.Reader). В обоих случаях само значение имеет фиксированный размер и может быть передано напрямую. Этот совет не относится к большим структурам или даже к небольшим структурам, которые могут расти.

...