dougua2309
dougua2309
2015-11-18 17:20

指针问题

TL;DR Somehow, I am appending a pointer to a list instead of the object within a for loop of objects so at the end the entire slice is composed of the same object multiple times. I just don't know how to fix that.

The Long Way

I am still having a super hard time trying to figure out pointers in go. I posted a question yesterday and got some help but now I am stuck on a slightly different issue in the same piece of code.

I am working with gocql and cqlr go packages to try and bit a small object mapper for my Cassandra queries. Essentially the problem I am having is I am appending what appears to be a pointer to an object, not a new instance of the obj to the array. How do I fix that? I have tried adding & and * in front of value but that doesn't seem to work. How do I fix these? The bind function needs an & according to their docs.

Code

type Query struct {
    query       string
    values      interface{}
    attempts    int
    maxAttempts int
    structType  reflect.Type
}

func (query Query) RetryingQuery() (results []interface{}) {
    var q *gocql.Query
    if query.values != nil {
        q = c.Session.Query(query.query, query.values)
    } else {
        q = c.Session.Query(query.query)
    }

    bindQuery := cqlr.BindQuery(q)
    value := reflect.New(query.structType).Interface()
    for bindQuery.Scan(value) {
        fmt.Println(value)
        results = append(results, value)
    }
    return
}

The docs ask for var value type then in bind you would pass &value. I quoted the docs below.

var t Tweet
var s []Tweet
for b.Scan(&t) {
    // Application specific code goes here
    append(s, t)
}

The issue is I cannot directly go var value query.structType to define its type then pass the reference of that to bindQuery.Scan().

What is printed

&{result1 x86_64 24 3.2.0-74-generic Linux}
&{result2 x86_64 24 3.19.0-25-generic Linux}
&{result3 x86_64 4 3.13.0-48-generic Linux}
&{result4 x86_64 2 3.13.0-62-generic Linux}
&{result5 x86_64 4 3.13.0-48-generic Linux}

What is in the slice

Spoiler, it is result5 repeated over and over. I understand that I am just appending the pointer to same object to the list and that every loop iteration the object is changed and that changes all the results in the slice to that new object. I just don't know how to fix it.

[{"hostname":"result5","machine":"x86_64","num_cpus":4,"release":"3.13.0-48-generic","sysname":"Linux"},{"hostname":"result5","machine":"x86_64","num_cpus":4,"release":"3.13.0-48-generic","sysname":"Linux"},{"hostname":"result5","machine":"x86_64","num_cpus":4,"release":"3.13.0-48-generic","sysname":"Linux"},{"hostname":"result5","machine":"x86_64","num_cpus":4,"release":"3.13.0-48-generic","sysname":"Linux"},{"hostname":"result5","machine":"x86_64","num_cpus":4,"release":"3.13.0-48-generic","sysname":"Linux"}]
  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

1条回答

  • douliao7930 douliao7930 6年前

    Well I can at least tell you what you're doing. bindQuery takes a pointer. It changes the value stored at the address.

    What you're essentially doing is this:

    package main
    
    import "fmt"
    
    func main() {
        var q int
        myInts := make([]*int, 0, 5)
        for i := 0; i < 5; i++ {
            q = i
            fmt.Printf("%d ", q)
            myInts = append(myInts, &q)
        }
        fmt.Printf("
    ")
        for _, value := range myInts {
            fmt.Printf("%d ", *value)
        }
        fmt.Printf("
    ")
        fmt.Println(myInts)
    }
    

    Which, as you can probably guess, gives you this:

    0 1 2 3 4 
    4 4 4 4 4 
    [0x104382e0 0x104382e0 0x104382e0 0x104382e0 0x104382e0]
    

    Things get a little more confusing with reflect. You can get your type as an interface, but that is it (unless you want to play with unsafe). An interface, in simple terms, contains a pointer to the original type underneath (and some other stuff). So in your function you are passing a pointer (and some other stuff). Then you're appending the pointer. It might be nice just to get concrete and type switch your interface. I assume you know what types it could be. In which case you'd have to have something along these lines:

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    type foo struct {
        fooval string
    }
    
    type bar struct {
        barval string
    }
    
    func main() {
        f1 := foo{"hi"}
        f2 := &foo{"hi"}
        b1 := bar{"bye"}
        b2 := &bar{"bye"}
    
        doSomething(f1)
        doSomething(f2)
        doSomething(b1)
        doSomething(b2)
    
    }
    
    func doSomething(i interface{}) {
        n := reflect.TypeOf(i)
        // get a new one
        newn := reflect.New(n).Interface()
        // find out what we got and handle each case
        switch t := newn.(type) {
        case **foo:
            *t = &foo{"hi!"}
            fmt.Printf("It was a **foo, here is the address %p and here is the value %v
    ", *t, **t)
        case **bar:
            *t = &bar{"bye :("}
            fmt.Printf("It was a **bar, here is the address %p and here is the value %v
    ", *t, **t)
        case *foo:
            t = &foo{"hey!"}
            fmt.Printf("It was a *foo, here is the address %p and here is the value %v
    ", t, *t)
        case *bar:
            t = &bar{"ahh!"}
            fmt.Printf("It was a *bar, here is the address %p and here is the value %v
    ", t, *t)
        default:
            panic("AHHHH")
        }
    }
    

    You could also just keep calling value = reflect.New(query.structType).Interface() inside of the loop which will give you new interfaces every time. Reassigning value after every append. Last time through the loop would make one extra though..

    点赞 评论 复制链接分享

为你推荐