为什么要在值上定义方法而不是指针上?

huangapple go评论79阅读模式
英文:

why define methods on values rather than pointers?

问题

浏览示例Go代码时,有些地方不一致。许多代码在指针类型上定义它们的方法,例如:

func (p *parser) parse() {...}

但是其他一些代码在类型本身上定义方法,而不是指向它的指针:

func (s scanner) scan() {...}

这样做有好的理由吗?通过值传递对象真的比通过指针更高效吗?

一个原因是“我不能改变这个对象”,但这对于大型对象来说是个问题(你会通过值传递大的结构体只是为了标记它不能被方法改变吗?)

英文:

Looking through sample Go codes, some things are not consistent. Many codes define their methods on pointer types, like:

func (p *parser) parse () {...}

But some other code define methods on just the type, not pointer to it:

func (s scanner) scan () {...}

Is there good reason to do the latter? Can it really be more efficient to pass object by value instead by pointer?

One reason is "I can't change this object", but this is problem with large objects anyway (would you pass big struct by value just to mark that it can not be changed by method?)

答案1

得分: 6

幸运的是,这在Go FAQ中有解释:

> 我应该在值上还是指针上定义方法?
>
func (s *MyStruct) pointerMethod() { } // 在指针上定义方法
func (s MyStruct) valueMethod() { } // 在值上定义方法
>
> 对于不习惯使用指针的程序员来说,上面这两个例子之间的区别可能会令人困惑,但实际情况非常简单。在为类型定义方法时,接收器(上面例子中的s)的行为就像它是方法的一个参数一样。因此,将接收器定义为值还是指针,实际上是同一个问题,就像函数参数应该是值还是指针一样。有几个考虑因素。

> 首先,也是最重要的,方法是否需要修改接收器?如果需要修改,接收器必须是指针。(切片和映射表现为引用,所以它们的情况稍微复杂一些,但是例如要在方法中更改切片的长度,接收器仍然必须是指针。)在上面的例子中,如果pointerMethod修改了s的字段,调用者将看到这些更改,但是valueMethod是使用调用者参数的副本调用的(这就是传递值的定义),所以它所做的更改对调用者是不可见的。

> 顺便说一下,指针接收器与Java中的情况完全相同,尽管在Java中指针是隐藏在底层的;而Go中的值接收器则是不寻常的。

> 其次是效率的考虑。如果接收器很大,比如一个大的结构体,使用指针接收器会更加便宜。

> 接下来是一致性的考虑。如果类型的某些方法必须具有指针接收器,那么其余的方法也应该具有指针接收器,这样方法集在使用类型的方式上是一致的。有关详细信息,请参阅方法集部分。

> 对于基本类型、切片和小结构体等类型,值接收器非常便宜,因此除非方法的语义要求使用指针,否则值接收器是高效和清晰的。

因此,是的,主要用于语义。知道一个方法是无副作用的是处理并发时的一个好事情,因为这意味着不需要加锁。除了全局变量和引用类型之外,值接收器是一个强烈的提示,表明你的方法是无副作用的。

英文:

Luckily, this is in the Go FAQ:

> Should I define methods on values or pointers?
>
func (s *MyStruct) pointerMethod() { } // method on pointer
func (s MyStruct) valueMethod() { } // method on value`
>
> For programmers unaccustomed to pointers, the distinction between
> these two examples can be confusing, but the situation is actually
> very simple. When defining a method on a type, the receiver (s in the
> above examples) behaves exactly as if it were an argument to the
> method. Whether to define the receiver as a value or as a pointer is
> the same question, then, as whether a function argument should be a
> value or a pointer. There are several considerations.
>
> First, and most important, does the method need to modify the
> receiver? If it does, the receiver must be a pointer. (Slices and maps
> act as references, so their story is a little more subtle, but for
> instance to change the length of a slice in a method the receiver must
> still be a pointer.) In the examples above, if pointerMethod modifies
> the fields of s, the caller will see those changes, but valueMethod is
> called with a copy of the caller's argument (that's the definition of
> passing a value), so changes it makes will be invisible to the caller.
>
> By the way, pointer receivers are identical to the situation in Java,
> although in Java the pointers are hidden under the covers; it's Go's
> value receivers that are unusual.
>
> Second is the consideration of efficiency. If the receiver is large, a
> big struct for instance, it will be much cheaper to use a pointer
> receiver.
>
> Next is consistency. If some of the methods of the type must have
> pointer receivers, the rest should too, so the method set is
> consistent regardless of how the type is used. See the section on
> method sets for details.
>
> For types such as basic types, slices, and small structs, a value
> receiver is very cheap so unless the semantics of the method requires
> a pointer, a value receiver is efficient and clear.

So yes, it is mainly used for semantics. Knowing that a method is side-effect free is a good thing to know when dealing with concurrency as this automatically means that no locking is required. Global variables and reference types aside, a value receiver is a strong hint that your method is side-effect free.

答案2

得分: 0

方法链式调用也是一个考虑因素。如果你想要写成

one.Two().Three()

那么要么 Two() 需要返回一个指针,要么 Three() 需要有一个值接收器,因为方法调用的结果不可寻址。如果 Two() 返回一个值,就无法取得它的地址以便调用一个指针接收器的方法。然而,如果 Three() 有一个值接收器,无论 Two() 是否返回一个指针,都可以正常工作。

英文:

Method chaining is also a consideration. If you want to be able to write

one.Two().Three()

then either Two() needs to return a pointer, or Three() needs to have a value receiver, because the result of a method call isn't addressable. If Two returns a value, it's not possible to take its address in order to call a pointer-receiver method on it. However, if Three() has a value receiver, it will work whether Two() returns a pointer or not.

huangapple
  • 本文由 发表于 2014年3月14日 21:35:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/22406530.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定