Pourquoi devrais-je faire () ou new ()?


203

Les documents d'introduction dédient de nombreux paragraphes pour expliquer la différence entre new()et make(), mais en pratique, vous pouvez créer des objets dans la portée locale et les renvoyer.

Pourquoi utiliseriez-vous la paire d'allocateurs?

Réponses:


170

Les choses que vous pouvez faire avec makecela que vous ne pouvez pas faire autrement:

  • Créer une chaîne
  • Créer une carte avec un espace préalloué
  • Créer une tranche avec espace pré-alloué ou avec len! = Cap

C'est un peu plus difficile à justifier new. La principale chose que cela facilite est la création de pointeurs vers des types non composites. Les deux fonctions ci-dessous sont équivalentes. On est juste un peu plus concis:

func newInt1() *int { return new(int) }

func newInt2() *int {
    var i int
    return &i
}

41
Il est vrai que «nouveau» ne peut pas être utilisé pour créer des chaînes. Cependant, à mon avis, le point est le suivant: que se passerait-il si «nouveau» et «faire» étaient réunis en une seule fonction intégrée? Certes, un tel remplacement serait possible. Puisqu'il est possible, la question est: quelles sont les raisons objectives d'avoir 2 fonctions intégrées plutôt qu'une seule fonction intégrée généralisée. - Votre réponse indique correctement que `` nouveau '' ne peut pas être utilisé pour créer des canaux / cartes / tranches, mais il ne fournit aucune justification quant à la raison pour laquelle Go a `` nouveau '' et `` faire '', au lieu d'avoir 1 fonction alloc + init généralisée.

5
Ils pouvaient être combinés et il a même été proposé par Rob Pike à un moment donné: groups.google.com/d/topic/golang-nuts/kWXYU95XN04/discussion . En fin de compte, cela n'a pas abouti pour des raisons similaires à celles données dans votre réponse.
Evan Shaw

12
La valeur effective indique que new renvoie une valeur mise à zéro, tandis que map alloue des types non mis à zéro map, slice ou channel. Voir golang.org/doc/effective_go.html#allocation_new
kristianp

Et au m := map[string]int{}lieu de m := make(map[string]int)? pas besoin de préallouer la taille également.
Noam Manos

165

Go propose plusieurs méthodes d'allocation de mémoire et d'initialisation de valeur:

&T{...}, &someLocalVar, new,make

L'allocation peut également se produire lors de la création de littéraux composites.


newpeut être utilisé pour allouer des valeurs telles que des entiers, &intest illégal:

new(Point)
&Point{}      // OK
&Point{2, 3}  // Combines allocation and initialization

new(int)
&int          // Illegal

// Works, but it is less convenient to write than new(int)
var i int
&i

La différence entre newet makepeut être vue en regardant l'exemple suivant:

p := new(chan int)   // p has type: *chan int
c := make(chan int)  // c has type: chan int

Supposons que Go n'a pas newet make, mais il a la fonction intégrée NEW. Ensuite, l'exemple de code ressemblerait à ceci:

p := NEW(*chan int)  // * is mandatory
c := NEW(chan int)

Le * serait obligatoire , donc:

new(int)        -->  NEW(*int)
new(Point)      -->  NEW(*Point)
new(chan int)   -->  NEW(*chan int)
make([]int, 10) -->  NEW([]int, 10)

new(Point)  // Illegal
new(int)    // Illegal

Oui, la fusion newet makeen une seule fonction intégrée est possible. Cependant, il est probable qu'une seule fonction intégrée entraînerait plus de confusion chez les nouveaux programmeurs Go que d'avoir deux fonctions intégrées.

Compte tenu de tous les points ci-dessus, il semble plus approprié newet makeséparé.


@TorstenBronger Je trouve que le nouveau est plus facile à lire et montre que c'est l'instance où le intest créé.
Daniel Toebe

4
Vouliez-vous écrire make(Point)et make(int)dans ces 2 dernières lignes?
Jimmy Huch

27

makeLa fonction alloue et initialise uniquement un objet de type tranche, carte ou chan. Comme new, le premier argument est un type. Mais, cela peut aussi prendre un deuxième argument, la taille. Contrairement à new, le type de retour de make est le même que le type de son argument, pas un pointeur sur celui-ci. Et la valeur allouée est initialisée (non mise à zéro comme dans new). La raison en est que tranche, carte et chan sont des structures de données. Ils doivent être initialisés, sinon ils ne seront pas utilisables. C'est la raison pour laquelle new () et make () doivent être différents.

Les exemples suivants de Efficace Go le montrent très clairement:

p *[]int = new([]int) // *p = nil, which makes p useless
v []int = make([]int, 100) // creates v structure that has pointer to an array, length field, and capacity field. So, v is immediately usable

1
Dans new([]int), il alloue simplement de la mémoire à [] int, mais ne s'initialise pas, il revient donc simplement nil; pas le pointeur vers la mémoire car il est inutilisable. make([]int)alloue et initialise pour qu'il soit utilisable, puis retourne son adresse.
o0omycomputero0o

12
  • new(T)- Alloue de la mémoire et la met à zéro pour le type T .. ..
    c'est-à-dire 0pour int , ""pour chaîne et nilpour les types référencés ( tranche , carte , chan )

    Notez que les types référencés ne sont que des pointeurs vers certaines structures de données sous-jacentes , qui ne seront pas créées par l' new(T)
    exemple: en cas de tranche , le tableau sous-jacent ne sera pas créé, donc ne new([]int) renvoie un pointeur vers rien

  • make(T)- Alloue de la mémoire pour les types de données référencés ( tranche , carte , chan ), plus initialise leurs structures de données sous-jacentes

    Exemple: en cas de tranche , le tableau sous-jacent sera créé avec la longueur et la capacité spécifiées
    Gardez à l'esprit que, contrairement à C, un tableau est un type primitif dans Go!


Cela étant dit:

  • make(T) se comporte comme une syntaxe littérale composite
  • new(T)se comporte comme var(lorsque la variable n'est pas initialisée)

    func main() {
        fmt.Println("-- MAKE --")
        a := make([]int, 0)
        aPtr := &a
        fmt.Println("pointer == nil :", *aPtr == nil)
        fmt.Printf("pointer value: %p\n\n", *aPtr)
    
        fmt.Println("-- COMPOSITE LITERAL --")
        b := []int{}
        bPtr := &b
        fmt.Println("pointer == nil :", *bPtr == nil)
        fmt.Printf("pointer value: %p\n\n", *bPtr)
    
        fmt.Println("-- NEW --")
        cPtr := new([]int)
        fmt.Println("pointer == nil :", *cPtr == nil)
        fmt.Printf("pointer value: %p\n\n", *cPtr)
    
        fmt.Println("-- VAR (not initialized) --")
        var d []int
        dPtr := &d
        fmt.Println("pointer == nil :", *dPtr == nil)
        fmt.Printf("pointer value: %p\n", *dPtr)
    }

    Exécutez le programme

    -- MAKE --
    pointer == nil : false
    pointer value: 0x118eff0  # address to underlying array
    
    -- COMPOSITE LITERAL --
    pointer == nil : false
    pointer value: 0x118eff0  # address to underlying array
    
    -- NEW --
    pointer == nil : true
    pointer value: 0x0
    
    -- VAR (not initialized) --
    pointer == nil : true
    pointer value: 0x0

    Pour en savoir plus:
    https://golang.org/doc/effective_go.html#allocation_new https://golang.org/doc/effective_go.html#allocation_make


  • les choses deviennent plus claires avec un exemple. a voté :)
    Sumit Jha

    8

    Vous devez make()créer des canaux et des cartes (et des tranches, mais ceux-ci peuvent également être créés à partir de tableaux). Il n'y a pas d'autre moyen de les créer, vous ne pouvez donc pas les supprimer make()de votre lexique.

    Quant à new(), je ne connais aucune raison pour laquelle vous en avez besoin lorsque vous pouvez utiliser la syntaxe struct. Il a cependant une signification sémantique unique, qui est "créer et renvoyer une structure avec tous les champs initialisés à leur valeur zéro", ce qui peut être utile.


    1
    Il faut donc éviter les nouveautés et préférer simplement l'utilisation de la syntaxe Struct
    CommonSenseCode

    8

    En dehors de tout ce qui est expliqué dans Effective Go , la principale différence entre new(T)et &T{}est que ce dernier effectue explicitement une allocation de tas. Cependant, il convient de noter que cela dépend de la mise en œuvre et peut donc être sujet à changement.

    La comparaison makeavec newn'a pas de sens car les deux remplissent des fonctions entièrement différentes. Mais cela est expliqué en détail dans l'article lié.


    10
    L'affirmation qui &T{}effectue explicitement une allocation de tas est AFAIK qui n'est basée sur rien dans les spécifications. En fait, je crois que les analyses d'échappement conservent déjà ce * T sur la pile autant que possible de la même manière qu'avec new(T).
    zzzz

    6

    new (T): il renvoie un pointeur pour taper T une valeur de type * T, il alloue et met à zéro la mémoire. new (T) est équivalent à & T {} .

    make (T): il retourne une valeur initialisée de type T , il alloue et initialise la mémoire. Son utilisé pour les tranches, la carte et les canaux.

    En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
    Licensed under cc by-sa 3.0 with attribution required.