经过 9 年的 C++ 开发,我正在探索 Go。在 C++ 中,通过值传递函数的参数是一种不好的做法,除了内置类型的变量之外,因为性能损失:参数的所有字段都将被复制,并且在大多数情况下,这将是一个非常昂贵的操作。
这对 Go 来说是真的吗?仅通过值传递“this”以将“const”语义分配给方法看起来非常昂贵。Go 编译器是否足够聪明,可以防止在第一次修改之前复制变量?为什么不像在 C/C++ 中那样在 Go 中按值传递“this”是一种反模式?
其他答案很好,但在我看来,缺少一些信息。
Go 中的接收器只是语法糖,如以下代码所示:
package main
import "fmt"
type Something struct {
Value int
}
func (s *Something) ChangeValue(n int) {
s.Value = n
}
func main() {
o := new(Something) // o is of type *Something
fmt.Println(o.Value) // Prints 0
o.ChangeValue(8) // Changes o.Value to 8
fmt.Println(o.Value) // Prints 8
(*Something).ChangeValue(o, 16) // Same as calling o.ChangeValue(16)
fmt.Println(o.Value) // Prints 16
}
基于此,考虑如果接收者ChangeValue
是一个类型的值Something
而不是指向一个的指针会发生什么......
这是正确的!您永远无法通过这种方法真正改变o
' 的Value
字段。大多数时候,您使用指针接收器来进行封装。
Go 中的“this”称为接收器。是的,仅使用非指针接收器来模拟“const”语义可能非常昂贵。但是 Go 出于充分的理由放弃了“const”修饰符。因此,以不必要的复制为代价来接管特定的语言设计决策可能不是一个好主意——在任何大于几个机器字的情况下。
顺便说一句,“this”或“self”和“receiver”之间的术语差异意味着它也具有不同的语义。IIRC,不能在其他一些语言中更改“this”或“self”的值,但在 Go 中,接收器只是另一个函数参数(实际上从编译器的角度来看是第一个)。
也就是说,这就是我不鼓励编写接收器变量名为this
or的方法的原因self
。对于习惯于其他语言的人来说,这是一种误导。
一个完全虚构的例子,希望能说明这个想法:
func (n *node) walk(f func(*node)) {
for n != nil {
f(n)
n = n.next
}
}
我想说你的 C++ 知识可以很好地转化为 Go 语言,了解作为函数参数(按值传递结构)和非函数参数(内置类型,例如 int)是昂贵的。
主要区别在于引用类型、切片、map
s 和channel
s。尽管它们看起来是按值传递的(您不需要使用指针),但它们实际上是通过引用传递的,因此通常不要使用指向切片、映射或通道的指针。
string
s 也很特殊——它们是底层的引用类型,但它们也是不可变的,所以直接传递它们。
至于this
在 Go 中调用的接收器或接收器的特定情况 - 适用相同的规则(请注意,与 C++ 不同,您可以将内置类型作为接收器),而且我认为编译器不够聪明,无法避免复制,所以对大型结构使用指针。
这取决于接收器的大小。如果接收器少于几十个字节,复制它实际上可能比传递指针时所需的指针追逐(额外的内存访问)便宜。此外,使用指针使得结构更有可能在堆上分配,给垃圾收集器带来额外的负担。
在 Go 中,副本始终是逐字节的副本,因此成本仅取决于结构的大小。在 C++ 中,它可能会调用复制构造函数,这可能会花费大量时间。
因此,除了非常大的对象外,只要根据方法的语义和与 API 其余部分的一致性,使用最有意义的任何接收器即可。