Richtige Methode, um einen Zeiger von einer Rcpp-Funktion auf ein "neues" Objekt zurückzugeben

9

Betrachten Sie 1) eine benutzerdefinierte Klasse mit einem potenziell großen Speicherdruck und 2) eine Funktion der obersten Ebene, die eine Vorverarbeitung durchführt und dann ein neues Objekt unserer benutzerdefinierten Klasse erstellt und zurückgibt. Um unnötiges Kopieren nach Wert zu vermeiden, weist die Funktion das Objekt zu und gibt stattdessen einen Zeiger darauf zurück.

Basierend auf einer früheren Diskussion scheint es, dass der richtige Weg, einen Zeiger auf ein neu erstelltes Objekt zurückzugeben, darin besteht, es mit einem Wrap zu versehen Rcpp::XPtr<>. R sieht es dann jedoch effektiv als externalptr, und ich kämpfe darum, den richtigen Weg zu finden, um es mit der Moderne RCPP_EXPOSED_CLASSund der RCPP_MODULEArt und Weise, Dinge zu tun, zu besetzen.

Die Alternative besteht darin, den Rohzeiger zurückzugeben. Aber dann bin ich nicht 100% sicher, dass der Objektspeicher richtig bereinigt wird. Ich lief valgrind, um auf Speicherlecks zu testen, und es wurden keine gefunden. Doch wer räumt auf? 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

Meine Frage ist, ob Rcpp::Xptr<>es der richtige Weg ist, Zeiger zurückzugeben, und wenn ja, wie kann ich R dazu bringen, das Ergebnis als Doublenicht zu sehen externalptr? Wenn das Zurückgeben eines Rohzeigers keine Speicherprobleme verursacht, wer bereinigt dann das von der Funktion erstellte Objekt?

Artem Sokolov
quelle
Ja, Sie möchten wahrscheinlich Rcpp::XPtreinen externen Zeiger aus C ++ - Code erstellen. Und du willst es machen double *oder was auch immer deine Nutzlast ist. Hier, in der Galerie, bei GitHub sollte es Beispiele geben ... Vielleicht können Sie mit einer motivierten Suche etwas finden, das nah genug ist?
Dirk Eddelbuettel
Hi @DirkEddelbuettel Die Besetzung muss es wirklich sein CustomClass*. Die eigentliche Anwendung ist eine benutzerdefinierte Datenstruktur ohne R-Äquivalent, und alle Interaktionen werden über die von der RCPP_MODULE. Die engste Übereinstimmung, die meine motivierte Suche gefunden hat, war ein Beitrag von vor 7 Jahren , in dem ich anscheinend einen template <> CustomClass* as()Konverter definieren muss . Ich bin mir jedoch nicht sicher, wie es mit RCPP_MODULEund interagieren soll RCPP_EXPOSED_CLASS, zumal ich dachte, dass letzteres bereits definiert ist wrap()und as().
Artem Sokolov
Romains Beitrag aus demselben Thread ist ebenfalls sehr hilfreich, hebt jedoch leider die Verwendung von Objekten direkt hervor, anstatt mit Zeigern umzugehen.
Artem Sokolov
1
Ich weiß, dass ich ähnliche Sachen gemacht habe, aber jetzt bin ich mir nicht sicher, was das beste Beispiel dafür ist. Sie können ein 'Singleton'-Objekt eindeutig einrichten und als Modul (RcppRedis) umbrechen. Ich glaube, ich habe getan, was Sie bei ein oder zwei früheren Jobs beschrieben haben, aber ich kann mir jetzt kein gutes öffentliches Beispiel vorstellen. Andererseits tun dies die verschiedenen Datenbank-Wrapper und das Zugriffspaket. Nicht das kleinste Thema, also vielleicht mit einer Spielzeug- / Mock-Implementierung beginnen und von dort aus bauen?
Dirk Eddelbuettel
Verwenden RCPP_EXPOSED_CLASSund RCPP_MODULEist wirklich der Weg, um es zu tun? Ich habe das noch nie benutzt oder gesehen.
F. Privé

Antworten:

7

Ich halte es für sinnvoll, die verschiedenen Ansätze getrennt zu betrachten. Dies macht die Unterscheidung klarer. Beachten Sie, dass dies der Diskussion in der Rcpp-Modul-Vignette ziemlich ähnlich ist.

Bei Verwendung haben Rcpp::XPtrSie Ihre Klasse und stellen exportierte C ++ - Funktionen für jede Methode bereit, die Sie verfügbar machen möchten:

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

Ausgabe:

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

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

> squareDouble(d2)
[1] 29.16

Beachten Sie, dass das Objekt in R nur ein "Zeiger" ist. Sie können eine S4 / RC / R6 / ... Klasse auf der R-Seite hinzufügen, wenn Sie etwas Schöneres wollen.

Das Umschließen des externen Zeigers in eine Klasse auf der R-Seite erhalten Sie kostenlos mithilfe von Rcpp-Modulen:

#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()
*/

Ausgabe:

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

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

> d1$square()
[1] 29.16

Es wird auch unterstützt, eine Factory-Methode anstelle eines Konstruktors in C ++ zu verwenden, jedoch mit identischer Verwendung auf der R-Seite:

#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()
*/

Ausgabe:

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

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

> d1$square()
[1] 29.16

Schließlich RCPP_EXPOSED_CLASSist praktisch , wenn Sie eine R - Seite Werksfunktion mit RCPP Module kombinieren möchten, da dies die erzeugt Rcpp::asund Rcpp::wrapbenötigt Erweiterungen Objekte eine weiter passieren zwischen R und C ++ unterstützen . Die Fabrik könnte functionwie Sie oder mithilfe von Rcpp-Attributen exportiert werden , was ich natürlicher finde:

#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()
*/

Ausgabe:

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

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

> d1$square()
[1] 29.16

In Bezug auf die Bereinigung: Sowohl Rcpp::XPtrals auch das Rcpp-Modul registrieren einen Standard-Finalizer, der den Destruktor des Objekts aufruft. Bei Bedarf können Sie auch einen benutzerdefinierten Finalizer hinzufügen.

Ich finde es schwierig, eine Empfehlung für einen dieser Ansätze abzugeben. Vielleicht ist es am besten, jeden von ihnen an einem einfachen Beispiel auszuprobieren und zu sehen, was Sie natürlicher finden.

Ralf Stubner
quelle
2
Sehr schönes Zeug. Sie sind hier auf einer Rolle.
Dirk Eddelbuettel
Vielen Dank. Das ist sehr hilfreich! Ich denke, es factoryist das Schlüsselverbindungsstück, das ich vermisst habe.
Artem Sokolov
Wissen Sie als kleines Follow-up zufällig, ob functionauch ein Finalizer registriert wird, oder ist es nur so factory ?
Artem Sokolov
1
@ArtemSokolov AFAIK Der Standard-Finalizer, der den Destruktor aufruft, wird von generiert class_<T>und ist unabhängig davon, wie das Objekt erstellt wird.
Ralf Stubner