如何克隆一个含有未导出字段的结构体?

7
如果我定义了一个类型为:
type T struct {
    S  string
    is []int
}

那么我该如何克隆这种类型的对象?如果我进行简单的赋值操作:
p := T{"some string", []int{10, 20}}
q := p

那么对 []int 的任何更改都会影响两个对象。由于 T.is 没有被导出,即使使用反射提取也无法显式复制它。

我目前在类型的包中提供了一个 Clone 方法。但这对其他包中的类似类型没有帮助。有没有其他方法可以做到这一点?

1个回答

9
你无法这样做。这就是未导出字段的目的:只有声明该字段的包才能修改它们。
请注意,如果T类型在另一个包中声明,则甚至不能编写以下代码:
p := somepackage.T{"some string", []int{10, 20}}

因为这样会隐式地尝试设置未导出的 T.is 字段,从而导致编译时错误:
implicit assignment of unexported field 'is' in somepackage.T literal

如果您拥有(或者可以修改)该程序包,最好提供一个Clone()方法或函数,或为类型T提供一个SetIs()方法。如果第三方程序包没有提供这样的功能,则无能为力。
请注意,虽然使用unsafe程序包可以做到这一点,但正如其名称所示:它是不安全的,您应该远离它。
另请注意,您可以创建T类型的新值,其中is未被复制,但将成为其类型的零值(在[]int的情况下将为nil):
var r somepackage.T
s := somepackage.T{S: p.S}

fmt.Printf("%q\n", r)
fmt.Printf("%q\n", s)

将输出:

{"" []}
{"some string" []}

但是你不能为未导出的字段 T.is 设置任何非零值。

请注意,您可以通过将其分配给另一个具有相同类型的结构变量(即“精确”复制)来制作具有未导出字段的结构的副本,这样会正确地复制未导出的字段。

就像这个例子:

type person struct {
    Name string
    age  *int
}

age := 22
p := &person{"Bob", &age}
fmt.Println(p)

p2 := new(person)
*p2 = *p
fmt.Println(p2)

这将输出(在 Go Playground上尝试):

&{Bob 0x414020}
&{Bob 0x414020}

我们甚至可以使用reflect来进行泛化,而不依赖于具体的类型:
type person struct {
    Name string
    age  *int
}

age := 22
p := &person{"Bob", &age}
fmt.Println(p)

v := reflect.ValueOf(p).Elem()
vp2 := reflect.New(v.Type())
vp2.Elem().Set(v)
fmt.Println(vp2)

请在Go Playground上尝试这个。

但我们无法做到的是改变未导出的person.age字段指向其他内容。除非有声明包的帮助,否则它只能为nil或相同的指针值(指向原始字段的对象)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接