我已经在#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
我很想听听你对此的看法。
我喜欢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) 考虑将我的解决方案推广一点与他人分享。
最有用的评论
我喜欢
post_clone()
方法的想法。 另一个想法,如#110 中所建议的,是将当前的clone
方法重命名为.clone
,然后默认定义clone = function() self$.clone()
并允许用户覆盖它。post_clone()
与.clone()
相比的一个优点是,因为它是从新对象运行的,所以它允许访问新对象的私有成员;.clone
方法,因为它是从旧对象运行的,所以只允许访问新对象的公共成员。 但我可以想象,在原始对象上,您会想要在克隆前和/或克隆后做一些事情。 所以两者都做可能是有意义的。这也将允许用户将
deep_clone=TRUE
设为类的默认值。我认为,当涉及到用自定义版本(#110 中建议的解决方法)完全替换内置 clone 方法时,基本上不可能期望类作者在一般情况下能够正确使用它,所以它会是好让他们调用 R6 的内置克隆代码,然后再按摩结果。 (为简单的类正确克隆并不太难,但是当继承和主动绑定发挥作用时,它变得非常非常困难。克隆测试目前有近 1000 行代码。)