2015-06-21 18 views
2

我正准备在C++中为R编写一个扩展包,并想知道如何使用动态内存管理而没有内存泄漏的风险。我已阅读当R扩展涉及到异常情况时,可以安全地分配内存吗?

,并立即得到了三个问题:

  1. 是否[R优雅地放松身心R-例外,例如的情况下,C++栈帧当R_alloc内存不足或Rf_error由于其他条件而被调用? - 否则,我应该如何清理R_alloc'ed和PROTECT ed或者仅仅是Calloc'ed内存?例如,将

    #include<R.h> 
    // […] 
    void someMethod() { 
        char *buffer1 = NULL; 
        char *buffer2 = NULL; 
        try { 
        ClassA a; 
        buffer1 = R_Calloc(10000, char); 
        buffer2 = R_Calloc(10000, char); 
        // […] 
        } finally { 
        try { 
         if (NULL != buffer1) { 
         R_Free(buffer1); 
         } 
        } finally { 
         if (NULL != buffer2) { 
         R_Free(buffer2); 
         } 
        } 
        } 
    } 
    

    保证调用析构函数~ClassAaR_Freebuffer1buffer2?如果不是,R教科书将如何保证这一点?

  2. 可以使用标准C++(现在不推荐使用)std::auto_ptr或现代的std::unique_ptr来简化内存分配习惯用法吗?
  3. 在C++标准模板库中使用R的内存分配是否有经过验证的C++习惯用法/最佳实践,例如:一些合适的分配器模板,以便STL类从R堆中分配它们的内存?
+0

该问题可能还涉及'Rf_warning',请参阅https://stackoverflow.com/questions/24557711/how-to-generate-an-r-warning-safely-in-rcpp –

+0

我看到的一个解决方案是编写我自己的垃圾收集器,例如以'R_alloc'及其朋友的包装器的形式,立即将指针分配给分配的内存,并带有一些全局内存管理对象,这至少可以在程序包卸载时释放泄漏的内存。但我有一些希望,有比这更好的做法。 –

回答

0

由于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_allocmalloc等完成。 (实验表明,从R_alloc存储器中的Rf_error情况下被自动释放。)

2:号

既不类有任何与注册v外部指针finalisers。

3:是的。

据我所看到的,它被认为是干净地分离C的统治++和R. RCPP鼓励使用包装纸(https://stat.ethz.ch/pipermail/r-devel/2010-May/057387.htmlcxxfunctionhttp://dirk.eddelbuettel.com/code/rcpp.html),使C++异常不会碰到R发动机的最佳实践。

在我看来,分配器可以编程为使用R_CallocR_Free。但是,为了在这种调用期间抵消潜在的Rf_error的影响,分配器需要一些接口来进行垃圾收集。我想在本地将分配器绑定到类型为externalptrPROTECT ed SEXP,其具有由R_RegisterCFinalizerEx注册的终结器并且指向本地存储器管理器,其在Rf_error的情况下可以释放存储器。

相关问题