在Go方法中通过值传递"this"会有性能损失吗?

Pav*_*pov 5 c++ go

经过9年的C++开发,我正在探索Go.在C++中,由于性能损失,除了内置类型的变量之外,通过值传递函数的参数是一种不好的做法:参数的所有字段都将被复制,并且在大多数情况下,这将是一个非常昂贵的操作.

对Go来说这是真的吗?通过值传递"this"仅仅为方法分配"const"语义看起来非常昂贵.Go编译器是否足够智能以防止在第一次修改之前复制变量?为什么不通过值将"this"传递给Go中的反模式,就像在C/C++中一样?

thw*_*hwd 9

其他答案很好,但在我看来,有一些信息缺失.

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
}
Run Code Online (Sandbox Code Playgroud)

基于此,考虑如果接收器ChangeValue是类型的值Something而不是指向一个的指针会发生什么...

那就对了!你永远不可能通过这种方法实际变异oValue字段.大多数情况下,您使用指针接收器进行封装​​.


zzz*_*zzz 6

Go中的"this"称为接收器.是的,使用非指针接收器仅模拟"const"语义可能非常昂贵.但Go出于好理由放弃了"const"修饰符.因此,以不必要的复制为代价来接管特定语言设计决策可能不是一个好主意 - 在任何大于机器字的情况下.

顺便说一句,"this"或"self"和"receiver"之间的术语差异意味着它也有不同的语义.IIRC,在一些其他语言中不能改变"this"或"self"的值,但在Go中,接收器只是另一个函数参数(实际上是编译器的第一个参数).

这就是说,这是我劝阻写作方法,其中接收器变量命名的原因thisself.对于习惯其他语言的人来说,这会产生误导.

一个完全构成的例子,有希望地说明这个想法:

func (n *node) walk(f func(*node)) {
        for n != nil {
                f(n)
                n = n.next
        }
}
Run Code Online (Sandbox Code Playgroud)

  • 考虑:`i int`,`p*T`,`s string`,`f*os.File`,`flag bool`,`n*node`,`r*root`.现在将所有变量(假设没有重新声明)重命名为`blah`.你认为它"_更具可读性"_?我不相信.将每个接收器命名为相同的名称,而不是给它一个提示(即读者有用的)名称对我来说不是"更具可读性",但不太可读.这只是一种习惯于语言的习惯.但是Go没有,所以没有理由在不存在限制的地方限制自己. (4认同)

Nic*_*ood 5

我会说你的C++知识将很好地转化为Go作为函数参数(通过值传递结构)和不存在的内容(内置类型,例如int).

主要区别在于引用类型,切片,maps和channels.虽然它们似乎是通过值传递的(你不需要使用指针)实际上是通过引用传递的,所以一般不要使用指向切片,映射或通道的指针.

strings也很特别 - 它们是引擎盖下的引用类型,但它们也是不可变的,所以直接传递它们.

至于this在Go中调用的特定情况或接收器 - 同样的规则适用(注意你可以将内置类型作为接收器而不像C++),我认为编译器不够智能以避免副本,所以使用指针表示大型结构.


and*_*olm 5

这取决于接收器的大小.如果接收器少于几十个字节,复制它实际上可能比指针追逐(额外的存储器访问)便宜,如果你传递一个指针就需要.此外,使用指针使得更有可能在堆上分配结构,这给垃圾收集器带来了额外的负担.

在Go中,副本始终是逐字节的副本,因此成本仅取决于结构的大小.在C++中,它可能会调用复制构造函数,这可能会占用大量时间.

因此,除了非常大的对象之外,根据方法的语义和与API其余部分的一致性,使用任何类型的接收器最有意义.