2016-12-18 147 views
1

我有一個第三方C庫用於編寫R擴展。我需要在庫中創建一些結構(並初始化它們),我需要將它們作爲S4對象的一部分進行維護(將這些結構視爲定義計算狀態,摧毀它們將會摧毀所有剩餘的計算和所有已經計算的結果)。 我想創建一個S4對象來保持指針這些結構爲void*指針,但它不完全清楚如何這樣做,什麼是槽的類型?S4指針指向C的對象struct

+1

https://stat.ethz.ch/R-manual/R-devel/library/methods/html/BasicClasses.html:'externalptr' – hrbrmstr

回答

5

正如@hrbrmstr指出的那樣,你可以使用externalptr型保持這樣的對象「活着」,這是在寫R附加的this section觸及,但我看不出有任何理由,你爲什麼會需要存儲任何東西如void*。如果您在使用一點C++時沒有任何問題,那麼Rcpp class XPtr可以消除管理EXTPTRSXP所涉及的大量樣板。舉個例子,假設以下簡化的例子代表您的第三方庫的API:

#include <Rcpp.h> 
#include <stdlib.h> 

typedef struct { 
    unsigned int count; 
    double total; 
} CStruct; 

CStruct* init_CStruct() { 
    return (CStruct*)::malloc(sizeof(CStruct)); 
} 

void free_CStruct(CStruct* ptr) { 
    ::free(ptr); 
    ::printf("free_CStruct called.\n"); 
} 

typedef Rcpp::XPtr<CStruct, Rcpp::PreserveStorage, free_CStruct> xptr_t; 

當通過new創建的指針是通常足夠使用Rcpp::XPtr<SomeClass>工作,因爲default finalizer只是調用舉行對象delete。但是,由於您正在處理C API,因此我們必須提供(默認)模板參數Rcpp::PreserveStorage,更重要的是提供適當的終結器(本例中爲free_CStruct),以便XPtr不會在通過malloc分配的內存上調用delete,當相應的R對象被垃圾收集時。

繼續該示例,假設你寫了以下功能與您的CStruct互動:

// [[Rcpp::export]] 
xptr_t MakeCStruct() { 
    CStruct* ptr = init_CStruct(); 
    ptr->count = 0; 
    ptr->total = 0; 

    return xptr_t(ptr, true); 
} 

// [[Rcpp::export]] 
void UpdateCStruct(xptr_t ptr, SEXP x) { 
    if (TYPEOF(x) == REALSXP) { 
     R_xlen_t i = 0, sz = XLENGTH(x); 
     for (; i < sz; i++) { 
      if (!ISNA(REAL(x)[i])) { 
       ptr->count++; 
       ptr->total += REAL(x)[i]; 
      } 
     } 
     return; 
    } 

    if (TYPEOF(x) == INTSXP) { 
     R_xlen_t i = 0, sz = XLENGTH(x); 
     for (; i < sz; i++) { 
      if (!ISNA(INTEGER(x)[i])) { 
       ptr->count++; 
       ptr->total += INTEGER(x)[i]; 
      } 
     } 
     return; 
    } 

    Rf_warning("Invalid SEXPTYPE.\n"); 
} 

// [[Rcpp::export]] 
void SummarizeCStruct(xptr_t ptr) { 
    ::printf(
     "count: %d\ntotal: %f\naverage: %f\n", 
     ptr->count, ptr->total, 
     ptr->count > 0 ? ptr->total/ptr->count : 0 
    ); 
} 

// [[Rcpp::export]] 
int GetCStructCount(xptr_t ptr) { 
    return ptr->count; 
} 

// [[Rcpp::export]] 
double GetCStructTotal(xptr_t ptr) { 
    return ptr->total; 
} 

// [[Rcpp::export]] 
void ResetCStruct(xptr_t ptr) { 
    ptr->count = 0; 
    ptr->total = 0.0; 
} 

在這一點上,你已經做了足夠的啓動與R處理CStructs

  • ptr <- MakeCStruct()將初始化CStruct並將其作爲externalptr存儲在R
  • UpdateCStruct(ptr, x)將修改存儲在CStruct數據,SummarizeCStruct(ptr)將打印摘要等
  • rm(ptr); gc()將刪除ptr對象並迫使垃圾收集器來運行,從而調用free_CStruct(ptr)和對事物的C側銷燬對象以及

您提到了S4類的使用,這是將所有這些函數包含在一個地方的一個選項。這裏有一個可能性:

setClass(
    "CStruct", 
    slots = c(
     ptr = "externalptr", 
     update = "function", 
     summarize = "function", 
     get_count = "function", 
     get_total = "function", 
     reset = "function" 
    ) 
) 

setMethod(
    "initialize", 
    "CStruct", 
    function(.Object) { 
     [email protected] <- MakeCStruct() 
     [email protected] <- function(x) { 
      UpdateCStruct([email protected], x) 
     } 
     [email protected] <- function() { 
      SummarizeCStruct([email protected]) 
     } 
     [email protected]_count <- function() { 
      GetCStructCount([email protected]) 
     } 
     [email protected]_total <- function() { 
      GetCStructTotal([email protected]) 
     } 
     [email protected] <- function() { 
      ResetCStruct([email protected]) 
     } 
     .Object 
    } 
) 

然後,我們可以用CStruct的工作是這樣的:

ptr <- new("CStruct") 
[email protected]() 
# count: 0 
# total: 0.000000 
# average: 0.000000 

set.seed(123) 
[email protected](rnorm(100)) 
[email protected]() 
# count: 100 
# total: 9.040591 
# average: 0.090406 

[email protected](rnorm(100)) 
[email protected]() 
# count: 200 
# total: -1.714089 
# average: -0.008570 

[email protected]() 
[email protected]() 
# count: 0 
# total: 0.000000 
# average: 0.000000 

rm(ptr); gc() 
# free_CStruct called. 
#   used (Mb) gc trigger (Mb) max used (Mb) 
# Ncells 484713 25.9  940480 50.3 601634 32.2 
# Vcells 934299 7.2 1650153 12.6 1308457 10.0 

當然,另一種選擇是使用Rcpp Modules,這或多或少請注意R側的類定義樣板(然而,使用參考類而不是S4類)。

+3

優秀的答案。可以(應該?)也可能是Rcpp Gallery的帖子! –