2013-03-10 134 views
5

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

这对Go来说是否正确?将“this”按值传递给该方法的“const”语义分配看起来非常昂贵。 Go编译器是否足够聪明以防止在第一次修改之前复制变量?为什么在Go中不像传入C/C++那样通过值来传递“this”?

+4

你的C++知识已经过时了:在许多情况下,通过值传递实际上是很好的(即使它们不是内置函数,它也总是可以运行很多类型)。以下是一个入门指南:http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ – 2013-03-10 19:01:52

+0

请向我们展示您担心的代码。在Go中,只能通过指针传递结构。如果你想要一个副本,你需要创建一个实际复制的函数。 – janislaw 2013-03-10 19:06:30

+2

@janislaw不正确的:http://play.golang.org/p/w5_6_w8JAn – zzzz 2013-03-10 19:13:42

回答

6

Go中的“this”被称为receiver。是的,使用非指针接收器来模仿“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 
     } 
} 
+0

非常有趣的例子 - 谢谢! – 2013-03-10 19:46:05

+0

我相信接收器在Python中的操作方式相同,但按照惯例,他们也称它为“自我”。我不明白为什么将你的接收器称为“this”或“self”会引起混淆......即使有人像对待Java中的“this”那样对待接收器,AFAICT也没有危险......我确实它始终使我的代码更易于我和其他面向对象语言的其他人阅读。 – weberc2 2013-03-12 13:45:27

+4

考虑:'我int','p * T','s string','f * os.File','flag bool','n * node','r * root'。现在将所有变量(假设没有重新声明)重命名为'blah'。你会认为它是“更可读”吗?我不这么认为。为每个接收者命名,而不是给它一个提示(即读者helpfull)名称对我来说不是“更具可读性”,但可读性较差。这只是强迫这种语言的一种习惯。但是Go没有,所以在限制不存在的地方没有理由限制自己。 – zzzz 2013-03-12 14:16:00

5

我想说你的C++知识将转化细到去什么作为函数参数(按值传递结构),什么ISN昂贵't(内建类型,例如int)。

主要差异将是参考类型,切片,map s和channel s。尽管它们看起来是按值传递的(你不需要使用指针)实际上是通过引用传递的,所以通常不使用指向片,映射或通道的指针。

string s也是特殊的 - 它们是引擎盖下的引用类型,但它们也是不可变的,所以直接传递它们。

至于this的特定情况或者在Go中调用的接收器 - 同样的规则适用(请注意,您可以将内置类型作为接收器,而不像C++),并且我认为编译器不够聪明为了避免副本,所以使用大型结构体的指针。

8

其他答案是好的,但在我看来,有一些信息丢失。

接收器在Go只是语法糖,这表现在以下code

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类型的值会发生什么一个...

没错!通过这种方法,你永远不可能实际改变oValue字段。大多数情况下,你使用指针接收器来封装。

5

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

在Go中,副本始终是逐字节副本,所以成本仅取决于结构的大小。在C++中,它可能会调用一个复制构造函数,这可能需要很长时间。

因此,除了真正的大对象之外,只要使用任何类型的接收器,基于方法的语义和与其他API的一致性就可以发挥最大意义。

相关问题