【问题标题】:Deep copying data structures in golanggolang 中的深度复制数据结构
【发布时间】:2019-10-14 17:59:49
【问题描述】:

我想复制一个数据结构的实例。由于 go 没有任何内置函数,因此我使用的是第三方库:https://github.com/emirpasic/gods

例如,我可以尝试使用带有哈希集的深拷贝。

var c, d hashset.Set
c = *hashset.New()
c.Add(1)
deepcopy.Copy(d, c)
c.Add(2)
fmt.Println(c.Contains(2))
fmt.Println(d.Contains(2))
fmt.Println(c.Contains(1))
fmt.Println(d.Contains(1))

但是,哈希集的内容根本没有被复制。我知道深拷贝模块不能复制未导出的值,但是由于库中没有内置的“复制构造函数”,这是否意味着在不修改其代码的情况下无法使用库完全复制数据结构实例? (我研究过的其他一些库也出现了类似的问题)。

我是 golang 的新手,感觉不对,因为类似的事情可以很容易地实现,例如在 C++ 中。我知道我可以编写自己的版本或修改他们的代码,但工作量比预期的要多,这就是为什么我认为应该有一种惯用的方式。

PS:对于可能会说“不需要这样的功能”的人,我正在将一些具有一些数据结构的复杂状态分发给并行计算线程,它们直接使用这些状态,并且不能相互干扰。

【问题讨论】:

  • 使用包reflect,如果我没记错的话,你应该能够使用unsafe实现你自己的深拷贝,包括未导出的字段。
  • @mkopriva: 但这听起来像是无中生有:) 能够深度复制所有字段确实很有用。
  • @mkopriva 您可以使用反射读取未导出的字段,但不能设置它们。见How to clone a structure with unexported field?
  • @icza stackoverflow.com/a/43918797/965900(我在最初的评论中确实提到了unsafe
  • @mkopriva 哦,抱歉,在您的第一条评论中没有发现 unsafe 字样。

标签: go struct deep-copy


【解决方案1】:

如果您需要深度复制 protobuf 结构,可以在 https://github.com/golang/protobuf 中使用 proto.Clone,更多帮助请参阅 godoc

【讨论】:

    【解决方案2】:

    如果您的结构是可序列化的,您可以将其转换为 JSON 并返回,这对我的用例来说已经足够了。

    func CloneMyStruct(orig *model.MyStruct) (*model.MyStruct, error) {
        origJSON, err := json.Marshal(orig)
        if err != nil {
            return nil, err
        }
    
        clone := &model.MyStruct{}
        if err = json.Unmarshal(origJSON, &clone); err != nil {
            return nil, err
        }
    
        return clone, nil
    }
    

    【讨论】:

      【解决方案3】:

      不幸与否,在 Go 中没有办法做到这一点。想到的第一个工具是反射(包reflect),但是使用反射只能读取未导出的字段,但不能设置它们。见How to clone a structure with unexported field?

      克隆具有未导出字段的结构的唯一方法是使用包unsafe(参见此处的示例:Access unexported fields in golang/reflect?),但正如其名称所说:它是不安全,你应该留下尽可能远离它。使用 unsafe 创建的程序不能保证它们可以继续使用更新的 Go 版本,或者它们在每个平台上的行为都相同。

      一般来说在 Go 中支持克隆的唯一且正确的方法是包本身是否支持此类操作。

      注意事项 #1:

      这并不意味着在某些特定情况下,您不能通过创建新值并手动构建其状态来“模仿”克隆。例如,您可以通过创建一个新映射来克隆 map,遍历原始映射的键值对并将它们设置在新映射中。

      注意 #2:

      请注意,您可以通过简单地将具有未导出字段的结构的“精确”副本assigning 复制到另一个结构变量(相同类型),这也将正确复制未导出的字段。

      就像这个例子:

      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或者同一个指针值(指向对象作为原始字段)。

      另见相关:Quicker way to deepcopy objects in golang

      【讨论】:

        【解决方案4】:

        如果您使用的包不提供数据结构的复制功能,那么我将编写自己的类型安全复制功能。此外,我不会担心未导出的字段,因为开发人员决定向用户隐藏这些字段一定是有原因的。

        在你的例子中:

        func NewHashSetCopy(src *hashset.Set) *hashset.Set{
            dst := *hashset.New()
            iterator := src.Iter()
            for elem := range iterator {
                dst.Add(elem)
            }
            return dst
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-10-04
          • 1970-01-01
          • 2020-04-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多