11

在 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("%d\n", 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)

我的问题是:正确处理此问题的最佳方法是什么?我想了一些策略,但是除了第一个,我不知道怎么做(以及是否可以做到):

  • 不要返回内部对象。用户总是拥有在内部转换(对于每个函数调用,例如 print 等)到内部 C++ 结构的 R 结构。优点:可以在没有段错误的情况下保存数据。缺点:这是非常低效的,尤其是当有大量数据时。
  • 将对象存储为 RC++ 结构,并尝试找出(我不知道如何)C++ 结构是否已损坏以重建它。优点:每个会话只构建一次内部数据结构。缺点:不是内存问题的最佳解决方案(数据存储两次)。
  • 仅使用 C++ 结构,从不保存/加载(使用的实际解决方案)。问题是没有错误消息,而只是一个段错误。
  • 找到一种在使用 R 保存/加载函数时序列化内部 C++ 对象的方法。

任何想法/建议都非常受欢迎。

4

2 回答 2

6

最后,我找到了 2 个解决方案来正确处理带有 C++ 对象的 R 保存/加载机制(数据持久性)。没有一个是完美的,但似乎总比什么都不做要好。很快,这些解决方案是(详情如下):

  • 使用 R Raw 数据类型存储 C++ 对象。这具有内存高效的优点,但在实践中非常复杂。就个人而言,我不会将它用于大型项目,因为内存管理可能很乏味。当然,使用模板类可能会有一些改进(如果你有想法,请分享)。
  • 将对象存储为 R 和 C++ 结构,并在重新加载后重建 C++ 对象。C++ 对象只构建一次(不是针对每个函数调用),但数据存储两次。这具有在大型项目中更容易实施的重大优势。

为了展示这些解决方案,我使用了一个稍微复杂一些的例子来强调第一个解决方案的复杂性。在本例中,我们的示例 C++ 对象是一种链表。

第一个解决方案:使用 R RAW 数据

这个想法(由@KarlForner 提出)是使用R Raw 数据类型来存储对象的内容。在实践中,这意味着:

  • 使用 R 原始数据类型分配内存。
  • 确保分配正确的内存量(这可能很复杂)。
  • 使用placement new 在指定的内存地址(带有原始数据的地址)处创建C++ 对象。

这是文件“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)

这个解决方案有几个问题:

  • 不调用析构函数(取决于您在析构函数中所做的事情,这可能是个问题)。
  • 如果您的类包含指向其他对象的指针,则所有指针都应相对于内存位置,以便在重新加载后使其工作(就像这里的链表一样)。
  • 这是相当复杂的。
  • 我不确定保存的文件 (.RData) 是否是跨平台的(跨计算机共享 RData 文件是否安全?),因为我不确定用于存储对象的内存是否相同所有平台。

第二种解决方案:将对象存储为 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)
于 2013-09-02T09:41:05.457 回答
2

您可以在 R 中加载 C++ 类(可能还有结构),这将变量保存在类范围内。R 将 C++ 分类为 S4 类。

在底层,一个指向 C++ 对象实例的指针被传递,因此您可以在 C++ 中创建一个对象实例,然后将它传递给 R 共享数据。

要了解如何做到这一点,它大约有 7 页,但非常值得一读,所以喝杯咖啡并结帐: Dirk Eddelbuettel 和 Romain François 用 Rcpp 模块公开 C++ 函数和类

pdf中的一个例子:

class Bar { 
public:

    Bar(double x_) : x(x_), nread(0), nwrite(0) {}

    double get_x() {
        nread++;
        return x; 
    }

    void set_x(double x_) {
        nwrite++;
        x = x_; 
    }

    IntegerVector stats() const { 
        return IntegerVector::create(_["read"] = nread,
                                     _["write"] = nwrite);
    }

private: 
    double x;
    int nread, nwrite;
};

RCPP_MODULE(mod_bar) {
    class_<Bar>( "Bar" )

    .constructor<double>()

    .property( "x", &Bar::get_x, &Bar::set_x )
    .method( "stats", &Bar::stats )
    ;
}

在 R 中:

Bar <- mod_bar$Bar 
b <- new(Bar, 10) 
b$x + b$x 
b$stats()
b$x <- 10 
b$stats()
于 2018-07-29T04:00:38.797 回答