我使用 Rcpp 包的Rcpp.package.skeleton函数创建了一个包,其中包含一个 .cpp 文件,其中 C++ 函数返回 0:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
RcppExport SEXP just_zero() {
BEGIN_RCPP
Rcpp::RNGScope __rngScope;
return wrap(0.0);
END_RCPP
}
Run Code Online (Sandbox Code Playgroud)
当我从 RI 安装并加载包时,可以通过 lapply 使用 .Call 调用该函数。正如预期的那样,它(似乎)总是返回 0:
> x <- lapply(seq(10000), function(i) { x <- .Call('just_zero'); stopifnot(x == 0); x } )
#*no errors!*
Run Code Online (Sandbox Code Playgroud)
但是,显然返回的值包含lapply非零:
> range(simplify2array(x))
[1] 0 3
Run Code Online (Sandbox Code Playgroud)
不幸的是,使用set.seed并不使这些返回值可重现,有时我确实得到[1] 0 0,有时得到其他值,例如[1] "0" "TRUE"。另一个线索是删除该线Rcpp::RNGScope __rngScope;可以解决问题。
为什么返回的对象中有非零元素lapply(特别是当我们检查了返回的值时.Call),以及 using 是如何RNGScope导致它的?
我在 Linux 和 OS X 上重现了这种行为。来自 OS X 的会话信息粘贴如下:
> sessionInfo()
R Under development (unstable) (2016-08-03 r71023)
Platform: x86_64-apple-darwin13.4.0 (64-bit)
Running under: OS X Mavericks 10.9.5
locale:
[1] en_GB.UTF-8/en_GB.UTF-8/en_GB.UTF-8/C/en_GB.UTF-8/en_GB.UTF-8
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] bug_1.0 devtools_1.12.0
loaded via a namespace (and not attached):
[1] tools_3.4.0 withr_1.0.2 memoise_1.0.0 Rcpp_0.12.6 git2r_0.15.0
[6] digest_0.6.10
Run Code Online (Sandbox Code Playgroud)
这是一个为我一致重现该问题的示例。
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
void dummy() {}
extern "C" SEXP just_zero() {
Rcpp::RNGScope rngScope;
return wrap(0.0);
}
/*** R
n <- 1E5
x <- unlist(lapply(seq(n), function(i) {
.Call('just_zero')
}))
unique(x)
*/
Run Code Online (Sandbox Code Playgroud)
调用Rcpp::sourceCpp()这个给了我例如
> unique(x)
[1] 0 8371 16021 20573 25109 43103 47563 56438 60852 78413 82773 95765
Run Code Online (Sandbox Code Playgroud)
是什么导致了这个问题?要理解这一点,我们需要理解 的定义RNGScope:我们可以在这里看到它:
它使用的方法定义如下:
https://github.com/RcppCore/Rcpp/blob/9fbdf78fe225607dc2c8af7267a6840af0aba10e/src/api.cpp#L63-L75
最重要的函数 ,PutRNGState定义如下:
just_zero现在,这实际上就是调用时发生的情况:
RNGScope创建对象,并准备好运行其关联的析构函数。wrap(0.0),即长度为 1、值为 0 的 a 。REALSXP请注意,该对象不受保护,因此有资格进行垃圾回收。RNGScope调用析构函数。PutRNGstate(),该例程本身也会调用allocVector,从而触发垃圾收集器。这意味着SEXP您想要返回的对象可以被收集,因此将是垃圾!所以,总而言之,使用 Rcpp 属性,因为它会为您安全地完成这一切。
要了解为什么 Rcpp 属性使此操作“安全”,请查看生成的代码:
// just_zero
SEXP just_zero();
RcppExport SEXP sourceCpp_0_just_zero() {
BEGIN_RCPP
Rcpp::RObject __result;
Rcpp::RNGScope __rngScope;
__result = Rcpp::wrap(just_zero());
return __result;
END_RCPP
}
Run Code Online (Sandbox Code Playgroud)
请注意,输出结果被分配给Rcpp::RObject,这会保护该对象,并且我们确保该对象是在该对象之前构造的,这确保了在析构函数运行RNGScope时它将保持受保护状态。RNGScope