如何处理R中的C ++内部数据结构以允许保存/加载?
在R中,我使用内部C ++结构来存储数据(使用R_ExternalPtr)。 然后可以使用各种功能处理数据。 这是一个简单的例子(真正的数据结构更复杂):
#include <Rinternals.h>
class MyObject{
public:
int type;
MyObject(int t):type(t){}
};
void finalizeMyObject(SEXP ptr){
MyObject * mo= static_cast<MyObject *>(R_ExternalPtrAddr(ptr));
delete mo;
}
extern "C" {
SEXP CreateObject(SEXP type) {
SEXP ans;
int nb = length(type);
PROTECT(ans=allocVector(VECSXP, nb));
int * typeP = INTEGER(type);
SEXP cl;
PROTECT(cl = allocVector(STRSXP, 1));
SET_STRING_ELT(cl, 0, mkChar("myObject"));
for(int i=0; i<nb; i++){
MyObject * mo = new MyObject(typeP[i]);
SEXP tmpObj = R_MakeExternalPtr(mo, R_NilValue, R_NilValue);
R_RegisterCFinalizerEx(tmpObj, (R_CFinalizer_t) finalizeMyObject, TRUE);
classgets(tmpObj, cl);
SET_VECTOR_ELT(ans, i, tmpObj);//Put in vector
}
UNPROTECT(2);
return ans;
}
SEXP printMyObject(SEXP myObjects){
int nb = length(myObjects);
for(int i=0; i<nb; i++){
SEXP moS=VECTOR_ELT(myObjects, i);
MyObject * mo= static_cast<MyObject *>(R_ExternalPtrAddr(moS));
Rprintf("%dn", mo->type);
}
return R_NilValue;
}
}
一旦建立了内部数据结构,就可以调用几个函数来计算不同的统计数据。 上面的函数printMyObject提供了这样一个函数的例子。
尝试保存此内部结构时出现问题。 它似乎保存了指针地址。 当对象重新加载时,我们会得到一个段错误。 这里是一个示例R代码(假设myobject.cpp包含上面的代码并被编译为myobject.dll)
dyn.load("myobject.dll")
## Create the internal data structure
xx <- .Call("CreateObject", 1:10)
##Everything is fine
.Call("printMyObject", xx)
## Save it, no errors
save(xx, file="xx.RData")
## remove all objects
rm(list=ls())
## Load the data
load(file="xx.RData")
##segfault
.Call("printMyObject", xx)
我的问题是:正确处理这个问题的最佳方法是什么? 我想到了一些策略,但除了第一个策略之外,我不知道如何完成(以及是否可以这样做):
任何想法/建议都非常受欢迎。
最后,我找到了两个解决方案来正确处理使用C ++对象(数据持久性)的R保存/加载机制。 没有一个是完美的,但似乎比无所事事好。 不久,这些解决方案(详情请见下文):
为了展示这些解决方案,我使用了一个稍微复杂的示例,以突出第一个解决方案的复杂性。 在这个例子中,我们的示例C ++对象是一种链表。
第一个解决方案:使用R RAW数据
这个想法(由@KarlForner提出)是使用R Raw数据类型来存储对象的内容。 实际上这意味着:
这里是文件“myobject.cpp”:
#include <Rinternals.h>
#include <new>
//Our example Object: A linked list.
class MyRawObject{
int type;
// Next object in the list.
bool hasnext;
public:
MyRawObject(int t):type(t), hasnext (false){
if(t>1){
//We build a next object.
hasnext = true;
//Here we use placement new to build the object in the next allocated memory
// No need to store a pointer.
//We know that this is in the next contiguous location (see memory allocation below)
new (this+1) MyRawObject(t-1);
}
}
void print(){
Rprintf(" => %d ", type);
if(this->hasnext){
//Next object located is in the next contiguous memory block
(this+1)->print(); //Next in allocated memory
}
}
};
extern "C" {
SEXP CreateRawObject(SEXP type) {
SEXP ans;
int nb = length(type);
PROTECT(ans=allocVector(VECSXP, nb));
int * typeP = INTEGER(type);
//When allocating memory, we need to know the size of our object.
int MOsize =sizeof(MyRawObject);
for(int i=0; i<nb; i++){
SEXP rawData;
//Allocate the memory using R RAW data type.
//Take care to allocate the right amount of memory
//Here we have typeP[i] objects to create.
PROTECT(rawData=allocVector(RAWSXP, typeP[i]*MOsize));
//Memory address of the allocated memory
unsigned char * buf = RAW(rawData);
//Here we use placement new to build the object in the allocated memory
new (buf) MyRawObject(typeP[i]);
SET_VECTOR_ELT(ans, i, rawData);//Put in vector
UNPROTECT(1);
}
UNPROTECT(1);
return ans;
}
SEXP printRawObject(SEXP myObjects){
int nb = length(myObjects);
for(int i=0; i<nb; i++){
SEXP moS=VECTOR_ELT(myObjects, i);
//Use reinterpret_cast to have a pointer of type MyRawObject pointing to the RAW data
MyRawObject * mo= reinterpret_cast<MyRawObject *>(RAW(moS));
if(mo!=NULL){
Rprintf("Address: %d", mo);
mo->print();
Rprintf("n");
}else{
Rprintf("Null pointer!n");
}
}
return R_NilValue;
}
}
这些函数可以直接在R中使用,例如:
## Create the internal data structure
xx <- .Call("CreateRawObject", 1:10)
##Print
.Call("printRawObject", xx)
## Save it
save(xx, file="xxRaw.RData")
## remove all objects
rm(xx)
## Load the data
load(file="xxRaw.RData")
##Works !
.Call("printRawObject", xx)
这个解决方案有几个问题:
第二种解决方案:将对象存储为R和C ++结构,并在重新加载后重建C ++对象。
这个想法是在每个函数调用中检查C ++指针是否正确。 如果不是,则重建该对象,否则忽略该步骤。 这是可能的,因为我们直接修改(在C ++中)R对象。 因此,此更改将对所有后续呼叫生效。 其优点是C ++对象只构建一次(不是针对每个函数调用),但数据存储两次。 这具有在大型项目上更容易实施的重要优势。
在文件“myobject.cpp”中。
#include <Rinternals.h>
//Our object can be made simpler because we can use pointers
class MyObject{
int type;
//Pointer to the next object
MyObject *next;
public:
MyObject(int t):type(t), next(NULL){
if(t>1){
next = new MyObject(t-1);
}
}
~MyObject(){
if(this->next!=NULL){
delete next;
}
}
void print(){
Rprintf(" => %d ", type);
if(this->next!=NULL){
this->next->print();
}
}
};
void finalizeMyObject(SEXP ptr){
MyObject * mo= static_cast<MyObject *>(R_ExternalPtrAddr(ptr));
delete mo;
}
extern "C" {
SEXP CreateObject(SEXP type) {
SEXP ans;
int nb = length(type);
PROTECT(ans=allocVector(VECSXP, nb));
int * typeP = INTEGER(type);
SEXP cl;
PROTECT(cl = allocVector(STRSXP, 1));
SET_STRING_ELT(cl, 0, mkChar("myObject"));
for(int i=0; i<nb; i++){
MyObject * mo = new MyObject(typeP[i]);
SEXP tmpObj = R_MakeExternalPtr(mo, R_NilValue, R_NilValue);
R_RegisterCFinalizerEx(tmpObj, (R_CFinalizer_t) finalizeMyObject, TRUE);
classgets(tmpObj, cl);
SET_VECTOR_ELT(ans, i, tmpObj);//Put in vector
}
UNPROTECT(2);
return ans;
}
SEXP printMyObject(SEXP myObjects){
int nb = length(myObjects);
for(int i=0; i<nb; i++){
SEXP moS=VECTOR_ELT(myObjects, i);
MyObject * mo= static_cast<MyObject *>(R_ExternalPtrAddr(moS));
if(mo!=NULL){
Rprintf("Address: %d", mo);
mo->print();
Rprintf("n");
}else{
Rprintf("Null pointer!n");
}
}
return R_NilValue;
}
//This function check if a C++ object is NULL, if it is, it rebuilds all the C++ objects.
SEXP checkRebuildMyObject(SEXP myObjects, SEXP type){
int nb = length(myObjects);
if(nb==0){
return R_NilValue;
}
if(R_ExternalPtrAddr(VECTOR_ELT(myObjects, 1))){ //Non corrupted ptrs
return R_NilValue;
}
int * typeP = INTEGER(type);
SEXP cl;
PROTECT(cl = allocVector(STRSXP, 1));
SET_STRING_ELT(cl, 0, mkChar("myObject"));
for(int i=0; i<nb; i++){
MyObject * mo = new MyObject(typeP[i]);
SEXP tmpObj = R_MakeExternalPtr(mo, R_NilValue, R_NilValue);
R_RegisterCFinalizerEx(tmpObj, (R_CFinalizer_t) finalizeMyObject, TRUE);
classgets(tmpObj, cl);
SET_VECTOR_ELT(myObjects, i, tmpObj);//Put in vector
}
UNPROTECT(1);
return R_NilValue;
}
}
在R方面,我们可以使用以下功能:
dyn.load("myobject.dll")
CreateObjectR <- function(type){
## We use a custom object type, that store both C++ and R data
type <- as.integer(type)
mo <- list(type=type, myObject= .Call("CreateObject", type))
class(mo) <- "myObjectList"
return(mo)
}
print.myObjectList <- function(x, ...){
## Be sure to check the C++ objects before calling the functions.
.Call("checkRebuildMyObject", x$myObject, x$type)
.Call("printMyObject", x$myObject)
invisible()
}
xx <- CreateObjectR(1:10)
print(xx)
save(xx, file="xx.RData")
rm(xx)
load(file="xx.RData")
print(xx)
链接地址: http://www.djcxy.com/p/15413.html
上一篇: How to handle C++ internal data structure in R in order to allow save/load?