Właściwy sposób na zwrócenie wskaźnika do obiektu `new` z funkcji Rcpp


9

Rozważ 1) klasę niestandardową z potencjalnie dużym drukiem pamięci oraz 2) funkcję najwyższego poziomu, która wykonuje wstępne przetwarzanie, a następnie tworzy i zwraca nowy obiekt naszej klasy niestandardowej. Aby uniknąć niepotrzebnego kopiowania według wartości, funkcja przydziela obiekt i zwraca do niego wskaźnik.

Na podstawie poprzedniej dyskusji wydaje się, że właściwym sposobem na zwrócenie wskaźnika do nowo utworzonego obiektu jest owinięcie go Rcpp::XPtr<>. Jednak R następnie widzi to skutecznie jako externalptr, i staram się znaleźć właściwy sposób, aby rzucić to na nowoczesność RCPP_EXPOSED_CLASSi RCPP_MODULEsposób robienia rzeczy.

Alternatywą jest zwrócenie surowego wskaźnika. Ale nie jestem w 100% pewien, że pamięć obiektów zostanie odpowiednio wyczyszczona. Pobiegłem valgrindsprawdzić, czy nie ma wycieków pamięci, ale nie znalazłem żadnych. Kto jednak sprząta? 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")
    ;
}

W 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

Moje pytanie brzmi: czy Rcpp::Xptr<>jest właściwy sposób zwracania wskaźników, a jeśli tak, to w jaki sposób sprawić, aby R zobaczył wynik jako Doublenie externalptr? Alternatywnie, jeśli zwrócenie surowego wskaźnika nie powoduje problemów z pamięcią, kto czyści obiekt utworzony przez funkcję?


Tak, prawdopodobnie chcesz Rcpp::XPtrutworzyć zewnętrzny wskaźnik z kodu C ++. I chcesz to rzucić, double *czy cokolwiek innego. Tu powinny być przykłady, w Galerii, na GitHub ... Może dzięki zmotywowanemu wyszukiwaniu możesz znaleźć coś wystarczająco blisko?
— Dirk Eddelbuettel,

Cześć @DirkEddelbuettel Obsada naprawdę musi być CustomClass*. Prawdziwa aplikacja to niestandardowa struktura danych bez odpowiednika R, a wszystkie interakcje odbywają się za pośrednictwem funkcji udostępnionej przez RCPP_MODULE. Najbliższym dopasowaniem, jakie znalazłem przy wyszukiwaniu motywowanym, był post sprzed 7 lat , w którym wydaje się, że muszę zdefiniować template <> CustomClass* as()konwerter. Nie jestem jednak pewien, w jaki sposób powinien on wchodzić w interakcje RCPP_MODULEi RCPP_EXPOSED_CLASS, zwłaszcza że myślałem, że to drugie zostało już zdefiniowane wrap()i as().
— Artem Sokolov,

Post Romaina z tego samego wątku jest również bardzo pomocny, ale niestety podkreśla bezpośrednio użycie obiektów, a nie obsługę wskaźników.
— Artem Sokolov,

1
Wiem, że zrobiłem podobne rzeczy, ale teraz nie jestem pewien, jaki to najlepszy przykład. Możesz wyraźnie ustawić obiekt „singleton” i zawinąć go jako moduł (RcppRedis); Myślę, że zrobiłem to, co opisałeś w poprzedniej pracy lub dwóch, ale nie mogę teraz wymyślić dobrego publicznego przykładu. Z drugiej strony - różne owijarki baz danych i pakiet dostępu to robią. Nie najmniejszy temat, więc może zacznij od implementacji zabawki / makiety i stamtąd buduj?
— Dirk Eddelbuettel,

Używanie RCPP_EXPOSED_CLASSi czy RCPP_MODULEto naprawdę sposób, aby to zrobić? Nigdy wcześniej tego nie używałem ani nie widziałem.
— F. Privé,

Odpowiedzi:


7

Myślę, że sensownie jest osobno spojrzeć na różne podejścia. To czyni rozróżnienie wyraźniejszym. Zauważ, że jest to dość podobne do dyskusji w winiecie Moduły Rcpp.

Podczas korzystania Rcpp::XPtrmasz klasę i udostępniasz wyeksportowane funkcje C ++ dla każdej metody, którą chcesz udostępnić:

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

Wynik:

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

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

> squareDouble(d2)
[1] 29.16

Zauważ, że w R obiekt jest tylko „wskaźnikiem”. Możesz dodać klasę S4 / RC / R6 / ... po stronie R, jeśli chcesz czegoś ładniejszego.

Zawijanie zewnętrznego wskaźnika w klasę po stronie R to coś, co otrzymujesz za darmo, używając modułów 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()
*/

Wynik:

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

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

> d1$square()
[1] 29.16

Obsługiwane jest również użycie metody fabrycznej zamiast konstruktora w C ++, ale z identycznym użyciem po stronie 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()
*/

Wynik:

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

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

> d1$square()
[1] 29.16

Wreszcie RCPP_EXPOSED_CLASSprzydaje się, jeśli chcesz połączyć funkcję fabryczną po stronie R z modułami Rcpp, ponieważ tworzy to Rcpp::asi Rcpp::wraprozszerzenia potrzebne do przekazywania obiektów z powrotem między R i C ++. Fabrykę można wyeksportować functiontak jak ty lub używając atrybutów Rcpp, co uważam za bardziej naturalne:

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

Wynik:

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

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

> d1$square()
[1] 29.16

Odnośnie czyszczenia: zarówno Rcpp::XPtrModuły, jak i Rcpp rejestrują domyślny finalizator, który wywołuje destruktor obiektu. W razie potrzeby możesz również dodać niestandardowy finalizator.

Trudno mi zalecić jedno z tych podejść. Może najlepiej wypróbować każdy z nich na prostym przykładzie i przekonać się, co jest bardziej naturalne w użyciu.


2
Bardzo fajne rzeczy. Jesteś na fali.
— Dirk Eddelbuettel,

Dziękuję Ci. To jest bardzo pomocne! Myślę, że factoryto kluczowy element złącza, którego mi brakowało.
— Artem Sokolov,

Jako małe uzupełnienie, czy wiesz, czy function rejestruje także finalizator, czy tylko to factory ?
— Artem Sokolov,

1
@ArtemSokolov AFAIK domyślny finalizator, który wywołuje destruktor, jest generowany przez class_<T> i jest niezależny od sposobu tworzenia obiektu.
— Ralf Stubner,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.