首页 文章

斯金格方法需要 Value

提问于
浏览
2

The Go FAQ answers a question关于方法中按值与按指针接收器定义的选择 . 该答案中的一个陈述是:

如果该类型的某些方法必须具有指针接收器,则其余方法也应如此,因此无论使用何种类型,方法集都是一致的 .

这意味着如果我为数据类型提供了一些改变数据的方法,因此需要按指针接收器,我应该使用by-pointer接收器来为该数据类型定义的所有方法 .

另一方面, "fmt" 包调用 Stringer 接口 by value 中定义的 String() 方法 . 如果使用接收器by-pointer定义 String() 方法,则当关联数据类型作为 fmt.Println (或其他 fmt 格式化方法)的参数给出时,不会调用它 . 这使得人们别无选择,只能通过值实现具有接收器的 String() 方法 .

如常见问题所示,如何满足 Stringer 接口的 fmt 要求,如何与按值与按指针的选择保持一致?

编辑:

为了强调我提到的问题的本质,考虑一种情况,其中一个数据类型具有一组用接收器按值定义的方法(包括String()) . 然后,人们希望添加一个改变该数据类型的附加方法 - 所以他用接收器指针定义它,并且(为了保持一致,根据FAQ答案),他还更新了要使用的数据类型的所有其他方法 . - 指针接收器 . 此更改对使用此数据类型的方法的任何代码没有任何影响 - 但是对于 fmt 格式化函数的调用(现在需要将指针传递给变量而不是其值,如更改之前) . 因此,一致性要求仅在 fmt 的背景下存在问题 . 需要调整基于接收器类型提供变量的方式(或类似功能)会破坏轻松重构一个包的能力 .

2 回答

  • 3

    为了强调我提到的问题的本质,考虑一种情况,其中一个数据类型具有一组用接收器按值定义的方法(包括String()) . 然后,人们希望添加一个改变该数据类型的附加方法 - 所以他用接收器指针定义它,并且(为了保持一致,根据FAQ答案),他还更新了要使用的数据类型的所有其他方法 . - 指针接收器 . 此更改对使用此数据类型的方法的任何代码没有影响 - 但是对于fmt格式化函数的调用(现在需要将指针传递给变量而不是其值,如更改之前) .

    这不是真的 . 它的所有 interface 和一些类型的断言也会受到影响 - 这就是fmt受到影响的原因 . 例如:

    package main
    
    import (
        "fmt"
    )
    
    type I interface {
        String() string
    }
    
    func (t t) String() string { return "" }
    
    func (p *p) String() string { return "" }
    
    type t struct{}
    type p struct{}
    
    func S(i I) {}
    
    func main() {
        fmt.Println("Hello, playground")
        T := t{}
        P := p{}
        _ = P
        S(T)
        //S(P) //fail
    }
    

    要从根中理解这一点,您应该知道指针方法和值方法与基础不同 . 但是,为方便起见,就像 ; 的省略一样,golang编译器使用没有指针的指针方法查找案例并将其更改回来 .

    正如这里所解释的:https://tour.golang.org/methods/6

    所以回到原始问题:指针方法的一致性 . 如果你仔细阅读faq,你会发现它是考虑使用值或指针方法的最后一部分 . 您可以在_1490408中找到标准lib示例中的反例:

    // A PriorityQueue implements heap.Interface and holds Items.
    type PriorityQueue []*Item
    
    func (pq PriorityQueue) Len() int { return len(pq) }
    
    func (pq PriorityQueue) Less(i, j int) bool {
        // We want Pop to give us the highest, not lowest, priority so we use greater than here.
        return pq[i].priority > pq[j].priority
    }
    
    func (pq PriorityQueue) Swap(i, j int) {
        pq[i], pq[j] = pq[j], pq[i]
        pq[i].index = i
        pq[j].index = j
    }
    
    func (pq *PriorityQueue) Push(x interface{}) {
        n := len(*pq)
        item := x.(*Item)
        item.index = n
        *pq = append(*pq, item)
    }
    
    func (pq *PriorityQueue) Pop() interface{} {
        old := *pq
        n := len(old)
        item := old[n-1]
        item.index = -1 // for safety
        *pq = old[0 : n-1]
        return item
    }
    
    // update modifies the priority and value of an Item in the queue.
    func (pq *PriorityQueue) update(item *Item, value string, priority int) {
        item.value = value
        item.priority = priority
        heap.Fix(pq, item.index)
    }
    

    事实上,正如FAQ所说,要确定是否使用指针方法,请按顺序进行以下考虑:

    • 该方法是否需要修改接收器?如果是,请使用指针 . 如果没有,应该有一个很好的理由或它会造成混乱 .

    • 效率 . 如果接收器很大,例如一个大的结构,使用指针接收器会便宜得多 . 但是,效率并不容易讨论 . 如果您认为这是一个问题,请在执行之前对其进行配置和/或基准测试 .

    • 一致性 . 如果该类型的某些方法必须具有指针接收器,则其余方法也应如此,因此无论使用何种类型,方法集都是一致的 . 对我来说,这意味着如果类型应该用作指针(例如,频繁修改),它应该使用方法集来标记 . 这并不意味着一种类型只能有指针方法,反之亦然 .

  • 2

    如果使用指针接收器定义方法,则应使用并传递指针值而不是非指针值 . 这样做传递的值确实实现了 Stringerfmt 包没有问题"detecting"并调用 String() 方法 .

    例:

    type Person struct {
        Name string
    }
    
    func (p *Person) String() string {
        return fmt.Sprintf("Person[%s]", p.Name)
    }
    
    func main() {
        p := &Person{Name: "Bob"}
        fmt.Println(p)
    }
    

    输出(在Go Playground上试试):

    Person[Bob]
    

    如果你将类型 Person 的值传递给 fmt.Println() 而不是 *Person 类型的指针,是的,确实不会调用 Person.String() . 但是如果 Person 的所有方法都有指针接收器,那么强烈表明你应该使用类型及其值作为指针(除非你不打算使用它的方法) .

    是的,您必须知道是否必须使用 Person*Person . 处理它 . 如果你想编写正确有效的程序,你必须知道的不仅仅是使用指针还是非指针值,我不知道,如果你很懒,请使用指针作为方法集(类型)指针值包含具有指针和非指针接收器的方法 .

    此外, Person 的作者可能会为您提供 NewPerson() 工厂函数,您可以依赖它返回正确类型的值(例如,如果方法具有值接收器,则为 Person ,如果方法具有指针接收器,则为 *Person ),因此您不会必须知道使用哪个 .

    Answer to later adding a method with pointer receiver to a type which previously only had methods with value receiver:

    是的,正如您在问题中所描述的那样,可能不会破坏现有代码,但继续使用非指针值可能无法从后来添加的指针接收器方法中获益 .

    我们可能会问:这是一个问题吗?使用类型时,您刚刚添加的新方法不存在 . 所以原始代码没有假设它的存在 . 所以它应该不是问题 .

    第二个考虑因素:类型只有具有值接收器的方法,因此可以很容易地假设通过它们的使用,该值是不可变的,因为具有值接收器的方法不能改变该值 . 使用该类型的代码可能已 Build 在此基础上,假设它没有被其方法更改,因此从多个goroutine中使用它可能正确地省略了某些同步 .

    所以我认为将一个带指针接收器的新方法添加到以前只有具有值接收器的方法的类型不应该是“不透明的”,添加这个新方法的人有责任修改这种类型的使用以“切换” “指针并确保代码保持安全和正确,或处理非指针值不具备此新方法的事实 .

    Tips:

    如果某个类型将来可能有mutator方法,您应该开始使用带有指针接收器的方法创建它 . 这样做可以避免以后必须完成上述过程 .

    另一个提示可能是完全隐藏类型,只发布接口 . 这样做,这种类型的用户不必知道接口是否包装指针,这无关紧要 . 它们接收接口值,并调用接口的方法 . 包作者负责处理正确的方法接收器,并返回实现接口的适当类型 . 客户看不到这一点,他们不依赖于此 . 他们所看到和使用的只是界面 .

相关问题