Considere 1) uma classe personalizada com uma impressão de memória potencialmente grande e 2) uma função de nível superior que realiza algum pré-processamento, depois cria e retorna um novo objeto de nossa classe personalizada. Para evitar cópias desnecessárias por valor, a função aloca o objeto e retorna um ponteiro para ele.
Com base em uma discussão anterior , parece que a maneira correta de retornar um ponteiro para um objeto recém-criado é envolvê-lo Rcpp::XPtr<>
. No entanto, R o vê efetivamente como externalptr
, e estou lutando para encontrar a maneira correta de interpretá-la com a maneira moderna RCPP_EXPOSED_CLASS
e RCPP_MODULE
de fazer as coisas.
A alternativa é retornar o ponteiro bruto. Mas não tenho 100% de certeza de que a memória do objeto seja devidamente limpa. Corri valgrind
para testar vazamentos de memória e não encontrei nenhum. No entanto, quem faz a limpeza? R?
test.cpp
#include <Rcpp.h>
// Custom class
class Double {
public:
Double( double v ) : value(v) {}
double square() {return value*value;}
private:
double value;
};
// Make the class visible
RCPP_EXPOSED_CLASS(Double)
// Option 1: returning raw pointer
Double* makeDouble( double x ) {
Double* pd = new Double(x);
return pd;
}
// Option 2: returning XPtr<>
SEXP makeDouble2( double x ) {
Double* pd = new Double(x);
Rcpp::XPtr<Double> ptr(pd);
return ptr;
}
RCPP_MODULE(double_cpp) {
using namespace Rcpp;
function( "makeDouble", &makeDouble );
function( "makeDouble2", &makeDouble2 );
class_<Double>("Double")
.constructor<double>("Wraps a double")
.method("square", &Double::square, "square of value")
;
}
Em R
Rcpp::sourceCpp("test.cpp")
d1 <- makeDouble(5.4) # <-- who cleans this up???
# C++ object <0x56257d628e70> of class 'Double' <0x56257c69cf90>
d1$square()
# 29.16
d2 <- makeDouble2(2.3)
# <pointer: 0x56257d3c3cd0>
d2$square()
# Error in d2$square : object of type 'externalptr' is not subsettable
Minha pergunta é se Rcpp::Xptr<>
é a maneira correta de retornar ponteiros e, se sim, como faço para que R veja o resultado como Double
não externalptr
? Como alternativa, se o retorno de um ponteiro bruto não causa problemas de memória, quem limpa o objeto que a função cria?
Rcpp::XPtr
criar um ponteiro externo a partir do código C ++. E você deseja transmiti-lodouble *
ou seja qual for sua carga útil. Deve haver exemplos aqui, na Galeria, no GitHub ... Talvez com uma pesquisa motivada você possa encontrar algo próximo o suficiente?CustomClass*
. O aplicativo real é uma estrutura de dados customizada sem equivalente em R e todas as interações são feitas através da funcionalidade exposta peloRCPP_MODULE
. A correspondência mais próxima que minha pesquisa motivada encontrou foi uma postagem de 7 anos atrás , onde parece que eu preciso definir umtemplate <> CustomClass* as()
conversor. No entanto, não estou claro como ele deve interagirRCPP_MODULE
eRCPP_EXPOSED_CLASS
, principalmente porque pensei que o último já definiuwrap()
eas()
.RCPP_EXPOSED_CLASS
eRCPP_MODULE
é realmente a maneira de fazê-lo? Eu nunca usei ou vi isso antes.Respostas:
Eu acho que faz sentido olhar para as diferentes abordagens separadamente. Isso torna a distinção mais clara. Observe que isso é bastante semelhante à discussão na vinheta dos módulos Rcpp.
Ao usar,
Rcpp::XPtr
você tem sua classe e fornece funções C ++ exportadas para todos os métodos que você deseja expor:Resultado:
Observe que em R o objeto é apenas um "ponteiro". Você pode adicionar uma classe S4 / RC / R6 / ... no lado R se quiser algo mais agradável.
Agrupar o ponteiro externo em uma classe no lado R é algo que você obtém gratuitamente usando os módulos Rcpp:
Resultado:
Também é suportado o uso de um método de fábrica em vez de um construtor em C ++, mas com uso idêntico no lado R:
Resultado:
Por fim,
RCPP_EXPOSED_CLASS
é útil se você deseja combinar uma função de fábrica do lado R com os módulos Rcpp, pois isso cria as extensõesRcpp::as
eRcpp::wrap
necessárias para passar os objetos entre R e C ++. A fábrica pode ser exportada viafunction
como você fez ou usando os Atributos Rcpp, o que acho mais natural:Resultado:
Em relação à limpeza:
Rcpp::XPtr
Os módulos Rcpp e Rcpp registram um finalizador padrão que chama o destruidor do objeto. Você também pode adicionar um finalizador personalizado, se necessário.Acho difícil fazer uma recomendação para uma dessas abordagens. Talvez seja melhor tentar cada um deles em um exemplo simples e ver o que você acha mais natural de usar.
fonte
factory
é a peça chave do conector que estou perdendo.function
também registra um finalizador, ou é apenasfactory
?class_<T>
e é independente de como o objeto é criado.