Rcpp-Modules:理解 .finalizer() 以及为什么它没有被调用

Rcpp-Modules: Understanding .finalizer() and why it doesn't get called

我写了一个名为 RMaxima 的 Rcpp 模块,它在调用构造函数时生成一个子进程。我提出这个问题的动机是,当 R 退出已分配并绑定到名称的函数执行环境时,我需要销毁此 class 的对象。

以下是我的源文件的重要部分:

class RMaxima 
{
  public:
    RMaxima()
    {
        ...

        // spawnes child process by instantiating an object 
        myMaxima = new Maxima(...); 
    }

    ~RMaxima()
    {
        // causes the child process to terminate properly
        delete myMaxima;
    }

    ...

  private:
    Maxima* myMaxima;
};

static void rmaxima_finalizer(RMaxima* ptr)
{
    if (ptr)
    { 
        delete ptr;
    }
}

RCPP_MODULE(Maxima)
{
    class_<RMaxima>("RMaxima")
    .constructor()
    .method(...)
    .finalizer(&rmaxima_finalizer)
    ;
} 

我对 R 中垃圾收集的理解是,当解释器退出环境时,来自该环境的值绑定将被丢弃,并且 R 的垃圾收集会为任何未绑定的值释放内存。

但是,这对于 Rcpp 模块似乎有所不同:如果我调用并退出创建我的 class

实例的函数
foo <- function() {
    m <- new(Rmaxima)
    ...
}

那么,不出所料,m并没有出现在全局环境中。但是,子进程仍然是运行。这意味着我的 class' 析构函数/终结器尚未被调用。但是,当我退出 R 会话或安装相应的自定义包时,它会被调用,在此期间测试加载。

为什么?我怎样才能让它在不同的范围内被破坏? “扩展 R”(钱伯斯出版社,2016 年)给了我一些提示

The Rcpp templated type allows conversions to and from "externalptr" with computations in the C++ code specialized to the type T of the object referred to. Templated code can in fact use 3 parameters: type, storage scope and a final- izer to be called when the object pointed to is deleted. The object returned to R is in all cases of class and type "externalptr". No information is available about the parametrized form.

指出 classes 返回为 externalptr 并受到 gc() 的保护,但我没有看到连接,因为对象是 typeof(m) : S4, 即引用 class.

任何进一步的提示,在哪里阅读这个?

您正在使用 Rcpp 模块,这在某种程度上可以替代您自己使用带有外部指针的东西。 Rcpp 为您提供 Rcpp::XPtr 并且您可以查看一些软件包使用的那些。 (我最喜欢的是快速 GitHub 搜索,如 this one across the 'cran' org that mirrors CRAN - 它显示超过 1k 的代码命中,因此需要进一步分类。)

并且 XPtr 本身似乎在 保证 访问终结器方面存在未解决的问题;我在去年提交了 issue #1108,并计划重新审视这个 'soon'。但是尽管这个问题可能是一个极端案例,但总的来说这似乎是有效的。所以我会研究 XPtr and/or XPtr 在 CRAN 上使用的两个帮助程序包。

最后,Rcpp 类 是由 John 编写和贡献的扩展。不过,他们还没有看到太多用途。 Rcpp 模块使用相当广泛,您也可以查看示例。

最后,你还可以做一些更简单直接的事情:

  • 用一个空的构造函数创建一个 class Foo
  • 具有指向 class
  • 的单例的静态指针
  • 创建一个 init 方法来设置 class 并让它拥有它需要的内存
  • 根据需要有 setter/getter/worker/result 方法
  • 创建一个 teardown 方法来释放内存

然后首先调用 init(可能来自包加载),然后是所有工作,然后确保调用拆卸。这是一个远非完美的设计(也许不叫拆解?)但是简单性给了你一个比较。一旦你有了碎片,你就可以在上面建造更奇特的结构。