Function 值接收器与指针接收器

Function 值接收器与指针接收器,function,pointers,go,Function,Pointers,Go,我很不清楚在哪种情况下,我希望使用值接收器,而不是始终使用指针接收器 要从文档中重述: type T struct { a int } func (tv T) Mv(a int) int { return 0 } // value receiver func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver 文档也说“对于基本类型、切片和小结构等类型,值接收器非常便宜,因此除非方法的语义需

我很不清楚在哪种情况下,我希望使用值接收器,而不是始终使用指针接收器

要从文档中重述:

type T struct {
    a int
}
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver
文档也说“对于基本类型、切片和小结构等类型,值接收器非常便宜,因此除非方法的语义需要指针,否则值接收器是高效和清晰的。”

第一点他们说值接收器“非常便宜”,但问题是它是否比指针接收器便宜。所以我做了一个小的基准测试,它向我展示了,即使对于只有一个字符串字段的结构,指针接收器也更快。结果如下:

// Struct one empty string property
BenchmarkChangePointerReceiver  2000000000               0.36 ns/op
BenchmarkChangeItValueReceiver  500000000                3.62 ns/op


// Struct one zero int property
BenchmarkChangePointerReceiver  2000000000               0.36 ns/op
BenchmarkChangeItValueReceiver  2000000000               0.36 ns/op
(编辑:请注意,第二点在较新的go版本中无效,请参阅注释。)

第二点医生们说,作为价值接受者,它是“高效和清晰的”,这更是一个品味问题,不是吗?就我个人而言,我更喜欢在任何地方使用相同的东西来保持一致性。什么意义上的效率?从性能上看,指针几乎总是更有效的。使用一个int属性的少数测试运行显示了值接收器的最小优势(范围为0.01-0.1 ns/op)

有谁能告诉我一个例子,一个值接收器比一个指针接收器显然更有意义?还是我在基准测试中做错了什么?我是否忽略了其他因素?

请注意

其次是一致性。如果该类型的某些方法必须具有指针接收器,那么其他方法也应该具有指针接收器,这样无论该类型如何使用,方法集都是一致的。有关详细信息,请参阅

如上所述:

关于接收器指针与值的规则是,值方法可以 可以对指针和值调用,但只能调用指针方法 关于指针

现在:

有谁能告诉我一个例子,一个值接收器比一个指针接收器更有意义

这有助于:

  • 如果接收器是map、func或chan,则不要使用指向它的指针
  • 如果接收器是一个切片,并且该方法没有重新切片或重新分配该切片,则不要使用指向它的指针
  • 如果方法需要改变接收器,接收器必须是指针
  • 如果接收器是包含
    sync.Mutex
    或类似同步字段的结构,则接收器必须是指针,以避免复制
  • 如果接收器是大型结构或数组,则指针接收器效率更高。有多大?假设它相当于将其所有元素作为参数传递给方法。如果感觉太大,对接收器来说也太大了
  • 函数或方法,无论是并发的还是从这个方法调用的,都可以改变接收者吗?值类型在调用方法时创建接收器的副本,因此外部更新不会应用于此接收器。如果更改必须在原始接收器中可见,则接收器必须是指针
  • 如果接收器是一个结构、数组或切片,并且它的任何元素都是指向可能发生变化的对象的指针,那么最好使用指针接收器,因为它会使读者更清楚地了解其意图
  • 如果接收器是一个小数组或结构,它自然是一个值类型(例如,类似于
    time.time
    类型),没有可变字段和指针,或者只是一个简单的基本类型,如int或string,值接收器是有意义的
    值接收器可以减少可生成的垃圾量;如果将值传递给值方法,则可以使用堆栈上的副本,而不是在堆上进行分配。(编译器试图巧妙地避免这种分配,但它不可能总是成功。)因此,如果不先进行分析,请不要选择值接收器类型
  • 最后,当有疑问时,使用指针接收器
例如,粗体部分可在以下位置找到:


为@VonC添加额外的内容,非常棒,信息丰富

我感到惊讶的是,一旦项目变得更大,老开发人员离开,新开发人员到来,没有人真正提到维护成本。围棋当然是一门年轻的语言

一般来说,我尽量避免使用指针,但它们确实有自己的位置和美感

我在下列情况下使用指针:

  • 使用大型数据集
  • 具有结构维护状态,例如令牌缓存
    • 我确保所有字段都是私有的,只有通过定义的方法接收器才能进行交互
    • 我不会把这个函数传递给任何goroutine
例如:

我避免使用指针的原因:

  • 指针不是同时安全的(整个GoLang点)
  • 一次指针接收器,总是指针接收器(用于所有结构的一致性方法)
  • 与“价值复制成本”相比,互斥锁无疑更昂贵、更慢、更难维护
  • 说到“价值复制成本”,这真的是一个问题吗?过早优化是万恶之源,您可以随时在以后添加指针
  • 它直接、简洁地迫使我设计小型结构
  • 通过设计具有明确意图和明显I/O的纯函数,可以避免指针
  • 我相信使用指针进行垃圾收集会更困难
  • 更容易就封装和责任进行争论
  • 保持简单,愚蠢(是的,指针可能很棘手,因为你永远不知道下一个项目的开发)
  • 单元测试就像走过粉色花园(只有斯洛伐克语表达?),意味着简单
  • 如果条件满足,则无NIL(如果需要指针,则可以传递NIL)
根据我的经验,编写尽可能多的封装方法,例如:

package rsa

// EncryptPKCS1v15 encrypts the given message with RSA and the padding scheme from PKCS#1 v1.5.
func EncryptPKCS1v15(rand io.Reader, pub *PublicKey, msg []byte) ([]byte, error) {
    return []byte("secret text"), nil
}

cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock) 
更新:

这个问题激发了我更多地研究这个话题,并写了一篇博文
type TokenCache struct {
    cache map[string]map[string]bool
}

func (c *TokenCache) Add(contract string, token string, authorized bool) {
    tokens := c.cache[contract]
    if tokens == nil {
        tokens = make(map[string]bool)
    }

    tokens[token] = authorized
    c.cache[contract] = tokens
}
package rsa

// EncryptPKCS1v15 encrypts the given message with RSA and the padding scheme from PKCS#1 v1.5.
func EncryptPKCS1v15(rand io.Reader, pub *PublicKey, msg []byte) ([]byte, error) {
    return []byte("secret text"), nil
}

cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock)