Modo corretto per restituire un puntatore a un `nuovo` oggetto da una funzione Rcpp


9

Considerare 1) una classe personalizzata con una memoria di memoria potenzialmente grande e 2) una funzione di livello superiore che esegue alcune preelaborazioni, quindi crea e restituisce un nuovo oggetto della nostra classe personalizzata. Per evitare copie non necessarie per valore, la funzione alloca l'oggetto e restituisce invece un puntatore ad esso.

Sulla base di una discussione precedente , sembra che il modo corretto di restituire un puntatore a un oggetto appena creato sia di avvolgerlo Rcpp::XPtr<>. Tuttavia, R lo vede in modo efficace externalptr, e sto lottando per trovare il modo giusto di lanciarlo con il moderno RCPP_EXPOSED_CLASSe il RCPP_MODULEmodo di fare le cose.

L'alternativa è restituire il puntatore non elaborato. Ma poi non sono sicuro al 100% che la memoria degli oggetti venga ripulita correttamente. Ho corso valgrindper verificare la presenza di perdite di memoria e non ne ho trovate. Tuttavia, chi pulisce? 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")
    ;
}

In 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

La mia domanda è se Rcpp::Xptr<>è il modo corretto di restituire i puntatori e, in tal caso, come posso ottenere R per vedere il risultato come Doubleno externalptr? In alternativa, se la restituzione di un puntatore non elaborato non causa problemi di memoria, chi pulisce l'oggetto creato dalla funzione?


Sì, probabilmente vuoi Rcpp::XPtrcreare un puntatore esterno dal codice C ++. E vuoi lanciarlo double *o qualunque sia il tuo payload. Ci dovrebbero essere esempi qui, alla Galleria, a GitHub ... Forse con una ricerca motivata puoi trovare qualcosa di abbastanza vicino?
Dirk Eddelbuettel,

Ciao @DirkEddelbuettel Il cast deve davvero esserlo CustomClass*. La vera applicazione è una struttura di dati personalizzata senza R equivalente e tutte le interazioni sono fatte attraverso la funzionalità esposta da RCPP_MODULE. La corrispondenza più vicina trovata dalla mia ricerca motivata è stata un post di 7 anni fa , in cui sembra che debba definire un template <> CustomClass* as()convertitore. Tuttavia, non sono chiaro su come dovrebbe interagire RCPP_MODULEe RCPP_EXPOSED_CLASS, soprattutto perché pensavo che quest'ultimo fosse già definito wrap()e as().
Artem Sokolov il

Anche il post di Romain dallo stesso thread è molto utile, ma sfortunatamente mette in evidenza l'uso degli oggetti direttamente, piuttosto che la gestione dei puntatori.
Artem Sokolov il

1
So di aver fatto cose simili ma ora non sono sicuro di quale sia il miglior esempio. È possibile impostare chiaramente un oggetto "singleton" e avvolgerlo come modulo (RcppRedis); Penso di aver fatto quello che descrivi in ​​uno o due lavori precedenti, ma non riesco a pensare a un buon esempio pubblico ora. Poi di nuovo: lo fanno i vari wrapper di database e il pacchetto di accesso. Non è l'argomento più piccolo, quindi forse iniziare con un'implementazione giocattolo / finta e costruire da lì?
Dirk Eddelbuettel,

utilizzando RCPP_EXPOSED_CLASS ed RCPP_MODULEè davvero il modo di farlo? Non l'ho mai visto o visto prima.
F. Privé,

Risposte:


7

Penso che abbia senso esaminare i diversi approcci separatamente. Questo rende la distinzione più chiara. Si noti che questo è abbastanza simile alla discussione nella vignetta dei moduli Rcpp.

Quando usi la Rcpp::XPtrtua classe e fornisci le funzioni C ++ esportate per ogni metodo che vuoi esporre:

#include <Rcpp.h>

// Custom class
class Double {
public:
    Double( double v ) : value(v) {}
    double square() {return value*value;}
private:
    double value;
};

// [[Rcpp::export]]
Rcpp::XPtr<Double> makeDouble(double x) {
    Double* pd = new Double(x);
    Rcpp::XPtr<Double> ptr(pd);
    return ptr;
}

// [[Rcpp::export]]
double squareDouble(Rcpp::XPtr<Double> x) {
    return x.get()->square();
}

/***R
(d2 <- makeDouble(5.4))
squareDouble(d2)
*/

Produzione:

> Rcpp::sourceCpp('59384221/xptr.cpp')

> (d2 <- makeDouble(5.4))
<pointer: 0x560366699b50>

> squareDouble(d2)
[1] 29.16

Si noti che in R l'oggetto è solo un "puntatore". Puoi aggiungere una classe S4 / RC / R6 / ... sul lato R se vuoi qualcosa di più bello.

Avvolgere il puntatore esterno in una classe sul lato R è qualcosa che ottieni gratuitamente usando i moduli Rcpp:

#include <Rcpp.h>

// Custom class
class Double {
public:
    Double( double v ) : value(v) {}
    double square() {return value*value;}
private:
    double value;
};

RCPP_MODULE(double_cpp) {
    using namespace Rcpp;

    class_<Double>("Double")
        .constructor<double>("Wraps a double")
        .method("square", &Double::square, "square of value")
    ;
}

/***R
(d1 <- new(Double, 5.4))
d1$square()
*/

Produzione:

> Rcpp::sourceCpp('59384221/modules.cpp')

> (d1 <- new(Double, 5.4))
C++ object <0x560366452eb0> of class 'Double' <0x56036480f320>

> d1$square()
[1] 29.16

È anche supportato l'uso di un metodo factory anziché un costruttore in C ++ ma con identico utilizzo sul lato R:

#include <Rcpp.h>

// Custom class
class Double {
public:
    Double( double v ) : value(v) {}
    double square() {return value*value;}
private:
    double value;
};

Double* makeDouble( double x ) {
    Double* pd = new Double(x);
    return pd;
}

RCPP_MODULE(double_cpp) {
    using namespace Rcpp;

    class_<Double>("Double")
        .factory<double>(makeDouble, "Wraps a double")
        .method("square", &Double::square, "square of value")
    ;
}

/***R
(d1 <- new(Double, 5.4))
d1$square()
*/

Produzione:

> Rcpp::sourceCpp('59384221/modules-factory.cpp')

> (d1 <- new(Double, 5.4))
C++ object <0x5603665aab80> of class 'Double' <0x5603666eaae0>

> d1$square()
[1] 29.16

Infine, RCPP_EXPOSED_CLASSè utile se si desidera combinare una funzione di fabbrica del lato R con i moduli Rcpp, poiché ciò crea le estensioni Rcpp::ase Rcpp::wrapnecessarie per far passare gli oggetti avanti e indietro tra R e C ++. La fabbrica potrebbe essere esportata functioncome hai fatto tu o usando gli attributi Rcpp, che trovo più naturali:

#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)

// [[Rcpp::export]]
Double makeDouble( double x ) {
    Double d(x);
    return d;
}

RCPP_MODULE(double_cpp) {
    using namespace Rcpp;

    class_<Double>("Double")
        .method("square", &Double::square, "square of value")
    ;
}

/***R
(d1 <- makeDouble(5.4))
d1$square()
*/

Produzione:

> Rcpp::sourceCpp('59384221/modules-expose.cpp')

> (d1 <- makeDouble(5.4))
C++ object <0x560366ebee10> of class 'Double' <0x560363d5f440>

> d1$square()
[1] 29.16

Per quanto riguarda la pulizia: entrambi i Rcpp::XPtrmoduli Rcpp registrano un finalizzatore predefinito che chiama il distruttore dell'oggetto. Puoi anche aggiungere un finalizzatore personalizzato, se necessario.

Trovo difficile dare una raccomandazione per uno di questi approcci. Forse è meglio provare ciascuno di essi su un semplice esempio e vedere cosa trovi più naturale da usare.


2
Roba molto bella. Sei su un rotolo qui.
Dirk Eddelbuettel,

Grazie. Questo è estremamente utile! Penso che factorysia il pezzo chiave del connettore che mi mancava.
Artem Sokolov,

Come piccolo follow-up, ti capita di sapere se functionregistra anche un finalizzatore o è solo factory ?
Artem Sokolov,

1
@ArtemSokolov AFAIK il finalizzatore predefinito che chiama il distruttore è generato da class_<T>ed è indipendente da come viene creato l'oggetto.
Ralf Stubner,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.