当命名类型 T 的任何方法具有指针接收器时,复制类型 T 的实例。

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

Copy instances of type T, when any of the methods of a named type T have a pointer receiver

问题

我最近阅读了《The Go Programming Language Book》,这是学习Go编程语言的好资源。
在第6.2节中有一段关于在方法中复制类型T的实例时,当它是指针接收器还是非指针接收器时的解释,我无法理解它。
有没有什么例子可以解释这段话的含义?

6.2 带有指针接收器的方法

如果命名类型T的所有方法的接收器类型都是T本身(而不是*T),那么复制该类型的实例是安全的;调用其任何方法都会自动复制。例如,time.Duration值会被大量复制,包括作为函数参数。但是,如果任何方法具有指针接收器,应避免复制T的实例,因为这样做可能会违反内部不变性。例如,复制bytes.Buffer的实例会导致原始实例和副本别名(§2.3.2)相同的底层数组。后续的方法调用会产生不可预测的效果。

(《The Go Programming Language》Alan A. A. Donovan · Brian W. Kernighan)

英文:

I read The Go Programming Language Book recently, the good resource for learning golang programming language.
There is a paragraph in 6.2 section about copy instance of type T when it is pointer receiver or not in methods, that I can't understand it.
Is there any that explain this paragraph with a meaningful example?

> 6.2 Methods with a Pointer Receiver
>
> If all the methods of a named type T have a receiver type of T itself (not *T ), it is safe to copy instances of that type; calling any of its methods necessarily makes a copy. For example, time.Duration values are liberally copied, including as arguments to functions. But if any method has a pointer receiver, you should avoid copying instances of T because doing so may violate internal invariants. For example, copying an instance of bytes.Buffer would cause the original and the copy to alias ( §2.3.2 ) the same underlying array of bytes. Subsequent method calls would have unpredictable effects.
>
> (The Go Programming Language Alan A. A. Donovan · Brian W. Kernighan)

答案1

得分: 5

当调用一个方法时,首先会复制该方法所调用的值,然后将该副本作为接收器传递/使用。

如果一个类型只有值接收器的方法,这意味着无论方法内部做什么,无论你(或其他人)调用什么方法,方法都无法改变原始值,因为如上所述,只有一个副本被传递,方法只能修改副本,而不能修改原始值。

所以这意味着如果你复制了该值,你就不必担心,无论是在原始值上调用的方法还是在副本上调用的方法都不能修改这些值。

然而,当类型具有指针接收器的方法时情况就不同了。如果一个方法具有指针接收器,该方法可以改变/修改指向的值,这不是一个副本,而是原始值(只有指针是一个副本,但它指向原始值)。

让我们看一个例子。我们创建一个int包装类型,它有两个字段:一个int和一个*int。我们打算在这两个字段中存储相同的数字,但一个是指针(我们将int存储在指向的值中):

type Wrapper struct {
    v int
    p *int
}

为了确保这两个值(v*p)相同,我们提供一个Set()方法,用于设置这两个值:

func (w *Wrapper) Set(v int) {
    w.v = v
    *w.p = v
}

Wrapper.Set()具有指针接收器(*Wrapper),因为它必须修改值(类型为Wrapper)。无论我们向Set()传递什么数字,我们可以确保一旦Set()返回,v*p都将相同,并且等于传递给Set()的数字。

现在,如果我们有一个Wrapper类型的值:

a := Wrapper{v: 0, p: new(int)}

我们可以在其上调用Set()方法:

a.Set(1)

编译器将自动取a的地址作为接收器使用,因此上述代码等同于(&a).Set(1)

我们期望Wrapper类型的任何值都在Wrapper.v*Wrapper.pv中存储相同的数字,只要使用Set()方法来更改字段的值。

现在,让我们看看如果我们复制a会出现什么问题:

a := Wrapper{v: 0, p: new(int)}
b := a
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)

a.Set(1)
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)

输出结果为:

a.v=0, a.p=0;  b.v=0, b.p=0
a.v=1, a.p=1;  b.v=0, b.p=1

我们复制了a(将其存储在b中),并打印了这些值。到目前为止一切正常。然后我们调用了a.Set(1),在此之后a仍然正常,但是b的内部状态变得无效:b.v不再等于*b.p。解释很清楚:当我们复制a(它是一个struct类型)时,会复制其字段的值(包括指针p),而b中的指针将指向与a中的指针相同的值。因此,修改指向的值将影响到Wrapper的两个副本,但我们有两个不同的v字段(它们不是指针)。

如果你的方法具有指针接收器,你应该使用指针值进行操作。

请注意,如果你复制一个*Wrapper值,一切仍然正常:

a := &Wrapper{v: 0, p: new(int)}
b := a
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)

a.Set(1)
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)

输出结果为:

a.v=0, a.p=0;  b.v=0, b.p=0
a.v=1, a.p=1;  b.v=1, b.p=1

在这种情况下,a是一个指针,它的类型是*Wrapper。我们复制了它(将其存储在b中),调用了a.Set()ab的内部状态都保持有效。在这里,我们只有一个Wrapper值,a只持有一个指向它的指针(它的地址)。当我们复制a时,我们只复制指针值,而不是struct值(类型为Wrapper)。

英文:

When calling a method, the value the method is called on is first copied, and that copy is passed / used as the receiver.

If a type only has methods with value receivers, that means no matter what the methods do inside, and no matter what methods you (or anyone else) call, the methods won't be able to change the original value, because –as noted above– only a copy is passed and the method could only modify the copy – and not he original.

So this means if you copy the value, you don't have to worry, neither the methods called on the original nor on the copy can't / won't modify the value(s).

Not, when the type has methods with pointer receivers. If a method has a pointer receiver, the method can change / modify the pointed value, which is not a copy, it's the original value (only the pointer is a copy but it points to the original value).

Let's see an example. We create an int wrapper type, which has 2 fields: an int and an *int. We intend to store the same number in both fields, but one is a pointer (and we store the int in the pointed value):

type Wrapper struct {
	v int
	p *int
}

To make sure both values (v and *p) are the same, we provide a Set() method, which sets both:

func (w *Wrapper) Set(v int) {
	w.v = v
	*w.p = v
}

Wrapper.Set() has a pointer receiver (*Wrapper) as it has to modify the value (which is of type Wrapper). No matter what number we pass to Set(), we can be sure that once Set() returns, both v and *p will be the same, and equal to the number passed to Set().

Now if we have a value of Wrapper:

a := Wrapper{v: 0, p: new(int)}

We can call the Set() method on it:

a.Set(1)

The compiler will automatically take the address of a to use as the receiver, so the above code means (&a).Set(1).

We'd expect that any value of type Wrapper has the same number stored in Wrapper.v and *Wrapper.pv, if only the Set() method is used to change the fields' values.

Now let's see the problem if we make a copy of a:

a := Wrapper{v: 0, p: new(int)}
b := a
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)

a.Set(1)
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)

Output (try it on the Go Playground):

a.v=0, a.p=0;  b.v=0, b.p=0
a.v=1, a.p=1;  b.v=0, b.p=1

We made a copy of a (stored it in b), and printed the values. So far so good. Then we called a.Set(1), after which a is still good, but internal state of b became invalid: b.v does not equal to *b.p anymore. The explanation is quite clear: when we made a copy of a (which is a struct type), that copies the values of its fields (including the pointer p), and the pointer in b will point to the same value as the pointer in a. Hence modifying the pointed value will affect both copies of Wrapper, but we have 2 distinct v fields (they are non-pointers).

If you have methods with pointer receivers, you should work with pointer values.

Note that if you would copy a value of *Wrapper, everything would still be cool:

a := &Wrapper{v: 0, p: new(int)}
b := a
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)

a.Set(1)
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)

Output (try it on the Go Playground):

a.v=0, a.p=0;  b.v=0, b.p=0
a.v=1, a.p=1;  b.v=1, b.p=1

In this case a is a pointer, it's of type *Wrapper. We made a copy of it (stored it in b), called a.Set(), and both the internal state of a and b remained valid. Here we only have one Wrapper value, a only holds a pointer to it (its address). When we copy a, we only make a copy of the pointer value, and not the struct value (of type Wrapper).

huangapple
  • 本文由 发表于 2017年1月5日 13:05:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/41477624.html
匿名

发表评论

匿名网友

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

确定