《编写R扩展》的第5.13,External pointers and weak references节规定:

外部指针SEXP旨在处理对C语言的引用 struct ,并在包中用于此目的 例如,RODBC.它们在复制语义方面是不寻常的,因为 复制R对象时,不会复制外部指针对象 复制的.(因此,外部指针应仅用作 具有正常语义的对象的一部分,例如属性或 列表中的元素.)

What is meant here by "external pointer object", the external pointer itself or the memory that the external pointer points to? Why would the unusual copying semantics mean that external pointers should only be used as part of an object with normal semantics?

澄清一下,我的R包是一个C库Baz的包装器.Baz库提供了一个C struct Foo,Baz将其用作一种内部工作空间.Baz提供C函数Foo* baz_allocate_foo()void baz_free_foo(Foo*)来分配和释放Foo个 struct ,这是在R内存管理之外完成的.

在我的R包中,我想使用外部指针来存储这Foo个已分配 struct 的地址.我的R包的部分C++代码(使用RCPP进行接口)如下所示:

// baz.h is the Baz C library header; contains the definition of struct Foo
#include <R.h>
#include <Rinternals.h>
extern "C" {
#include <baz.h>
}

// For use as the external pointer's tag
#define FOO_CODE 0xF00C0DE

// Finalizer for garbage collection of external pointers to Foo
void finalize_foo(SEXP x)
{
    baz_free_foo(reinterpret_cast<Foo*>(R_ExternalPtrAddr(x)));
    R_ClearExternalPtr(x);
}

// Exported function for users of my R package
// [[Rcpp::export]]
SEXP get_foo()
{
    SEXP tag = PROTECT(Rf_ScalarInteger(FOO_CODE));
    SEXP x = PROTECT(R_MakeExternalPtr(baz_allocate_foo(), tag, R_NilValue));
    R_RegisterCFinalizerEx(x, finalize_foo, TRUE);
    UNPROTECT(2);
    return x;
}

在R代码中,用户会这样做:

myfoo = bazwrap::get_foo()
bazwrap::say_hello(myfoo, "Alice")
bazwrap::say_goodbye(myfoo, "Bob")

其目的是在对myfoo进行垃圾回收时或在R退出之前释放myfoo指向的内存.

因此,我在这里使用的是一个完全独立的外部指针,而不是作为列表的一部分,也不是像编写R扩展所建议的那样,作为具有"正常语义"的对象的属性.我没有发现这有任何问题,即使在做例如

library(bazwrap)
myfoo1 = get_foo()
myfoo2 = myfoo1
rm(myfoo2)
gc() # as expected, this does not trigger the finalizer as myfoo1 is still around
say_hello(myfoo1, "Alice") # doesn't crash...

这就引出了我在帖子顶部用粗体提出的问题.

推荐答案

这里,"异常复制语义"只是指C[1]中的duplicate1(s, .)返回s,而不是s的副本.这样的语义被用于外部指针和9个其他类型;事实上,在duplicate.c中,我们看到duplicate1做了类似的事情:

switch (TYPEOF(s)) {
case NILSXP:
case SYMSXP:
case ENVSXP:
case SPECIALSXP:
case BUILTINSXP:
case EXTPTRSXP:
case BCODESXP:
case WEAKREFSXP:
case CHARSXP:
case PROMSXP:
    return s;

你的主要问题的答案与属性的设置有关. 对于上述10种类型之一的sattr(s, name) <- value要么是错误(NILSXPSYMSXPCHARSXP),要么在s上设置属性,而不是s的副本. 是否引用s没有区别;因为duplicate1是一个空操作,所以永远不会进行复制.

考虑到这一点,我们可以通过查看环境(ENVSXP)上的属性来理解外部指针(EXTPTRSXP)上的属性,这与R级API的便利性完全相似:

> e1 <- e2 <- new.env()
> attr(e2, "a")
NULL
> attr(e1, "a") <- 0
> attr(e2, "a")
[1] 0

也就是说,在环境上设置属性会影响对该环境的所有其他引用.外部指针也可以这么说.因此,Writing R Extensions manualLuke Tierney's original notes中的建议是始终将外部指针放在列表或配对列表中,duplicate1确实会复制:

> e1 <- e2 <- list(new.env())
> attr(e2, "a")
NULL
> attr(e1, "a") <- 0
> attr(e2, "a")
NULL

如果您想知道,鉴于外部指针不寻常的复制语义,为什么在外部指针上设置属性不是错误,那么不要忘记,已分类的外部指针必须具有class属性.我想R-core希望允许有类的外部指针,即使它的建议是使用包含无类外部指针的有类列表或对列表.


  1. API函数duplicateshallow_duplicate只是duplicate1的包装器.

R相关问答推荐

使用scale_x_continuous复制ggplot 2中的离散x轴

是否可以 Select 安装不带文档的R包以更有效地存储?

带有gplot 2的十字舱口

R创建一个数据透视表,计算多个组的百分比

如何删除gggvenn与gggplot绘制的空白?

如何在geom_col中反转条

根据日期从参考帧中创建不同的帧

从非重叠(非滚动)周期中的最新数据向后开窗并在周期内计数

如何从向量构造一系列双边公式

R -如何分配夜间GPS数据(即跨越午夜的数据)相同的开始日期?

如何移除GGPlot中超出与面相交的任何格网像元

从数据创建数字的命名列表.R中的框

在R中,如何从一系列具有索引名的变量快速创建数据帧?

构建一个6/49彩票模拟系统

带有Bootswatch Cerulean主题的shiny 仪表板中的浏览&按钮可见性问题

GgHighlight找不到它创建的列:`Highlight..1`->;`Highlight.....`

R中的交叉表

只有当我在循环的末尾放置一条print语句时,Foreach才会给出预期的输出

向数据添加标签

用LOOCV进行K近邻问题