R6: 使用相互引用的对象克隆类

创建于 2019-02-07  ·  4评论  ·  资料来源: r-lib/R6

我已经在#110 中对此发表了评论,但我想clone(deep = TRUE)一个具有相互引用的成员对象的对象。

一个例子如下: r2包含对象y ,它引用对象x 。 在原始对象obj1可以通过y的引用更改x ,但在克隆obj2

rr = R6::R6Class("test",
  public = list(
    ref = NULL,
    val = 0,
    initialize = function(ref) {
      self$ref <- ref
    }
  )
)

r2 = R6::R6Class("test",
  public = list(
    x = NULL,
    y = NULL,
    initialize = function() {
      self$x <- rr$new(NULL)
      self$y <- rr$new(self$x)
    }
  )
)

obj1 <- r2$new()
obj2 <- obj1$clone(deep = TRUE)

obj1$y$ref$val <- 100
print(obj1$x$val)  # prints '100', as it should

obj2$y$ref$val <- 200
print(obj2$x$val)  # prints '0'

@dfalbel发布了一个解决方法,但有两个缺点:(1) 完全覆盖clone方法意味着必须从头开始构造对象,以及 (2) 该方法没有得到很好的支持并且实际上似乎正在解决R6::R6Class内置的限制,该限制似乎试图阻止自定义clone方法。

在完成大部分clone()工作后,可以选择让某些代码执行,以便对克隆对象进行一些后处理,这将很有用。 例如,在上面的示例中,可以修复y$ref对象中的引用。 我建议的一个 API 是让用户可以选择定义一个private$post_clone(old_self)方法,该方法在clone()的末尾被调用。 所需的更改可能是插入行

if (deep && has_private && is.function(new[[1]]$private$post_clone)) {
  new[[1]]$private$post_clone(old_1_binding)
}
new_1_binding

在他克隆功能的最后

我很想听听你对此的看法。

feature

最有用的评论

我喜欢post_clone()方法的想法。 另一个想法,如#110 中所建议的,是将当前的clone方法重命名为.clone ,然后默认定义clone = function() self$.clone()并允许用户覆盖它。

post_clone().clone()相比的一个优点是,因为它是从新对象运行的,所以它允许访问新对象的私有成员; .clone方法,因为它是从旧对象运行的,所以只允许访问新对象的公共成员。 但我可以想象,在原始对象上,您会想要在克隆前和/或克隆后做一些事情。 所以两者都做可能是有意义的。

这也将允许用户将deep_clone=TRUE设为类的默认值。

我认为,当涉及到用自定义版本(#110 中建议的解决方法)完全替换内置 clone 方法时,基本上不可能期望类作者在一般情况下能够正确使用它,所以它会是好让他们调用 R6 的内置克隆代码,然后再按摩结果。 (为简单的类正确克隆并不太难,但是当继承和主动绑定发挥作用时,它变得非常非常困难。克隆测试目前有近 1000 行代码。)

所有4条评论

我喜欢post_clone()方法的想法。 另一个想法,如#110 中所建议的,是将当前的clone方法重命名为.clone ,然后默认定义clone = function() self$.clone()并允许用户覆盖它。

post_clone().clone()相比的一个优点是,因为它是从新对象运行的,所以它允许访问新对象的私有成员; .clone方法,因为它是从旧对象运行的,所以只允许访问新对象的公共成员。 但我可以想象,在原始对象上,您会想要在克隆前和/或克隆后做一些事情。 所以两者都做可能是有意义的。

这也将允许用户将deep_clone=TRUE设为类的默认值。

我认为,当涉及到用自定义版本(#110 中建议的解决方法)完全替换内置 clone 方法时,基本上不可能期望类作者在一般情况下能够正确使用它,所以它会是好让他们调用 R6 的内置克隆代码,然后再按摩结果。 (为简单的类正确克隆并不太难,但是当继承和主动绑定发挥作用时,它变得非常非常困难。克隆测试目前有近 1000 行代码。)

我主要使用 R6 来包装 C++ 类。 在我的用例中,我有一个self$pointer并且所有方法都将此指针发送到 cpp 进行计算。 克隆这个类时,我只需要在cpp端创建一个新对象和一个新指针,然后创建一个Class$new(cpp_ptr)对象。 在这种情况下,覆盖克隆方法行为似乎是安全的。

另外,在这里不修改clone行为是错误的。 使用self$clone根本不会克隆该类。

我喜欢post_clone想法,我认为它涵盖了大多数用例,但恕我直言, .clone方法将是更好的方法。

我也非常喜欢post_clone.clone方法的想法。 我有一个与 #178 几乎相同的问题 pegeler/eddington2#4。

目前我已经设置了cloneable = FALSE并计划 _try_ 创建一个自定义克隆方法。 但是,正如@wch指出的那样,考虑到我迄今为止设置课程的方式,这最终可能会非常困难。 幸运的是,通过对我的方法进行一些修改,我可以采取一些捷径使事情变得更容易。 :笑脸:

是否考虑过在此软件包的未来版本中实现这些方法之一?

许多使用嵌套 R6 对象进行克隆挑战的示例都使用了相对简单的o1 -> o2 -> o1引用循环(例如 #110 中提供的copy解决方案)。 我想知道在深度克隆(即 o1 -> o2 -> o3 -> ... -> o1)时是否有对话/计划(或根本没有兴趣)尝试解决参考图中更普遍的循环问题. 在其他基于引用的语言中,这些克隆操作通常需要在递归克隆过程中进行一些状态管理(即通过递归调用堆栈共享状态)。

是否已经为 R6 类讨论过(或正在开发中)这些方法?
我正在考虑为我的一些用例实现我自己的(非常简化和特定的)版本,所以我想知道我是否应该(i)也许只是等待,(ii)加入/收听正在进行的关于类似努力的对话,或 (iii) 考虑将我的解决方案推广一点与他人分享。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

rappster picture rappster  ·  3评论

mattwarkentin picture mattwarkentin  ·  7评论

paulstaab picture paulstaab  ·  3评论

wch picture wch  ·  8评论

gaborcsardi picture gaborcsardi  ·  22评论