由于 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
对 in 指针的赋值R_ClearExternalPtr( handle );
防止了 R_Free(pointer);` 的双重调用。
请注意,建议的习惯用法仍然需要一些假设才能安全工作:如果构造函数不能在 R 的意义上失败,即通过调用Rf_error
. 如果这无法避免,我的建议是将构造函数调用推迟到终结器注册之后,以便终结器在任何情况下都能够R_Free
记忆。但是,必须包含逻辑才能不调用析构函数~A
,除非A
对象已被有效构造。在简单的情况下,例如当A
只包含原始字段时,这可能不是问题,但在更复杂的情况下,我建议包装A
到一个struct
可以记住是否A
构造函数成功完成,然后为该结构分配内存。当然,我们仍然必须依靠A
构造函数来优雅地失败,释放它分配的所有内存,无论这是由C_alloc
ormalloc
还是其他人完成的。(实验表明,R_alloc
在 的情况下,内存会自动释放Rf_error
。)
2:没有。
这两个类都与注册 R 外部指针终结器无关。
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
,分配器将需要一些垃圾收集接口。我想在本地将分配器绑定到一个PROTECT
edSEXP
类型externalptr
,该类型具有一个由注册的终结R_RegisterCFinalizerEx
器并指向一个本地内存管理器,该管理器可以在Rf_error
.