由于Rf_error确实会跳过C++堆栈帧并因此绕过析构调用,所以我发现有必要进行更多的文档研究。特别是看RODBC封装和实验监测内存使用来确认发现,使我到达:
1:立即将指针存储在R外部指针并为此注册一个终结器。
成语在以下有些简单化例子所示:
#define STRICT_R_HEADERS true
#include <string>
#include <R.h>
#include <Rinternals.h> // defines SEXP
using namespace std;
class A {
string name;
public:
A (const char * const name) : name(name) { Rprintf("Construct %s\n", name); }
~A() { Rprintf("Destruct %s\n", name.c_str()); }
const char* whoami() const { return name.c_str(); }
};
extern "C" {
void finaliseAhandle (SEXP handle) {
A* pointer = static_cast<A*>(R_ExternalPtrAddr(handle));
if (NULL != pointer) {
pointer->~A();
R_Free(pointer);
R_ClearExternalPtr(handle);
}
}
SEXP createAhandle (const SEXP name) {
A* pointer = R_Calloc(1, A);
SEXP handle = PROTECT(R_MakeExternalPtr(
pointer,
R_NilValue, // for this simple example no use of tag and prot
R_NilValue
));
try {
new(pointer) A(CHAR(STRING_ELT(name, 0)));
R_RegisterCFinalizerEx(handle, finaliseAhandle, TRUE);
} catch (...) {
R_Free(pointer);
R_ClearExternalPtr(handle);
Rf_error("construction of A(\"%s\") failed", CHAR(STRING_ELT(name, 0)));
}
// … more code may follow here, including calls to Rf_error.
UNPROTECT(1);
return handle;
}
SEXP nameAhandle (const SEXP handle) {
A* pointer = static_cast<A*>(R_ExternalPtrAddr(handle));
if(NULL != pointer) {
return mkChar(pointer->whoami());
}
return R_NilValue;
}
SEXP destroyAhandle (const SEXP handle) {
if(NULL != R_ExternalPtrAddr(handle)) {
finaliseAhandle(handle);
}
return R_NilValue;
}
}
的NULL
到指针在R_ClearExternalPtr(handle);
分配防止R_Free(指针)的两次调用;`。
请注意,建议的习惯用法仍然需要一些安全工作所需的假设:如果构造函数不能在R的意义上失败,即调用Rf_error
。如果这无法避免,我的建议是将构造函数调用推迟到最终注册之后,以便在任何情况下终结者都能够对内存进行调整。然而,除非A
对象已被有效构造,否则必须包含逻辑以便不致调用析构函数~A
。在简单的情况下,例如当A
只包含原始字段时,这可能不是问题,但在更复杂的情况下,我建议将A
换成struct
,然后可以记住A
构造函数是否成功完成,然后为该结构分配内存。当然,我们仍然必须依靠A
构造函数来正常地失败,释放它分配的所有内存,而不管这是否由C_alloc
或malloc
等完成。 (实验表明,从R_alloc
存储器中的Rf_error
情况下被自动释放。)
2:号
既不类有任何与注册v外部指针finalisers。
3:是的。
据我所看到的,它被认为是干净地分离C的统治++和R. RCPP鼓励使用包装纸(https://stat.ethz.ch/pipermail/r-devel/2010-May/057387.html,cxxfunction
在http://dirk.eddelbuettel.com/code/rcpp.html),使C++异常不会碰到R发动机的最佳实践。
在我看来,分配器可以编程为使用R_Calloc
和R_Free
。但是,为了在这种调用期间抵消潜在的Rf_error
的影响,分配器需要一些接口来进行垃圾收集。我想在本地将分配器绑定到类型为externalptr
的PROTECT
ed SEXP
,其具有由R_RegisterCFinalizerEx
注册的终结器并且指向本地存储器管理器,其在Rf_error
的情况下可以释放存储器。
该问题可能还涉及'Rf_warning',请参阅https://stackoverflow.com/questions/24557711/how-to-generate-an-r-warning-safely-in-rcpp –
我看到的一个解决方案是编写我自己的垃圾收集器,例如以'R_alloc'及其朋友的包装器的形式,立即将指针分配给分配的内存,并带有一些全局内存管理对象,这至少可以在程序包卸载时释放泄漏的内存。但我有一些希望,有比这更好的做法。 –