Cara yang tepat untuk mengembalikan pointer ke objek `baru` dari fungsi Rcpp

9

Pertimbangkan 1) kelas khusus dengan cetakan memori yang berpotensi besar, dan 2) fungsi tingkat atas yang melakukan beberapa pra-pemrosesan, kemudian buat dan kembalikan objek baru dari kelas khusus kami. Untuk menghindari penyalinan yang tidak perlu dengan nilai, fungsi mengalokasikan objek dan mengembalikan pointer ke sana.

Berdasarkan diskusi sebelumnya , tampaknya cara yang tepat untuk mengembalikan pointer ke objek yang baru dibuat adalah dengan membungkusnya Rcpp::XPtr<>. Namun, R kemudian melihatnya secara efektif externalptr, dan saya berjuang untuk menemukan cara yang tepat untuk melemparkannya dengan cara modern RCPP_EXPOSED_CLASSdan RCPP_MODULEmelakukan sesuatu.

Alternatifnya adalah mengembalikan pointer mentah. Tapi kemudian saya tidak 100% yakin bahwa memori objek dibersihkan dengan benar. Saya berlari valgrinduntuk menguji kebocoran memori, dan tidak menemukan apa pun. Namun, siapa yang membersihkan? 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")
    ;
}

Dalam 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

Pertanyaan saya adalah apakah Rcpp::Xptr<>cara yang tepat untuk mengembalikan pointer, dan jika demikian, bagaimana saya membuat R untuk melihat hasilnya Double, bukan externalptr? Atau, jika mengembalikan pointer mentah tidak menyebabkan masalah memori, siapa yang membersihkan objek yang dibuat fungsi?

Artem Sokolov
sumber
Ya, Anda mungkin ingin Rcpp::XPtrmembuat pointer eksternal dari kode C ++. Dan Anda ingin melakukan itu double *atau apa pun muatan Anda. Seharusnya ada contoh di sini, di Galeri, di GitHub ... Mungkin dengan pencarian termotivasi Anda dapat menemukan sesuatu yang cukup dekat?
Dirk Eddelbuettel
Hai @DirkEddelbuettel Para pemain benar-benar perlu CustomClass*. Aplikasi sebenarnya adalah struktur data khusus tanpa R yang setara dan semua interaksi dilakukan melalui fungsionalitas yang diekspos oleh RCPP_MODULE. Kecocokan terdekat dengan pencarian termotivasi yang saya temukan adalah pos dari 7 tahun yang lalu , di mana sepertinya saya perlu mendefinisikan template <> CustomClass* as()konverter. Namun, saya tidak jelas bagaimana harus berinteraksi dengan RCPP_MODULEdan RCPP_EXPOSED_CLASS, terutama karena saya pikir yang terakhir sudah didefinisikan wrap()dan as().
Artem Sokolov
Posting Romain dari utas yang sama juga sangat membantu, tapi sayangnya itu menyoroti penggunaan objek secara langsung, daripada menangani pointer.
Artem Sokolov
1
Saya tahu saya telah melakukan hal serupa tetapi sekarang saya tidak yakin apa contoh terbaik ini. Anda dapat dengan jelas mengatur objek 'tunggal' dan membungkusnya sebagai modul (RcppRedis); Saya pikir saya telah melakukan apa yang Anda gambarkan pada pekerjaan sebelumnya atau dua, tetapi saya tidak bisa memikirkan contoh publik yang baik sekarang. Kemudian lagi - berbagai pembungkus basis data dan paket akses melakukan itu. Bukan topik terkecil, jadi mungkin mulai dengan implementasi mainan / tiruan dan membangun dari sana?
Dirk Eddelbuettel
Menggunakan RCPP_EXPOSED_CLASSdan RCPP_MODULEbenar-benar cara untuk melakukannya? Saya belum pernah menggunakan atau melihat itu sebelumnya.
F. Privé

Jawaban:

7

Saya pikir masuk akal untuk melihat pendekatan yang berbeda secara terpisah. Ini membuat perbedaan menjadi lebih jelas. Perhatikan bahwa ini sangat mirip dengan diskusi dalam sketsa Modul Rcpp.

Saat menggunakan Rcpp::XPtrAnda memiliki kelas dan menyediakan fungsi C ++ yang diekspor untuk setiap metode yang ingin Anda paparkan:

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

Keluaran:

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

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

> squareDouble(d2)
[1] 29.16

Perhatikan bahwa dalam R objek hanya "penunjuk". Anda bisa menambahkan kelas S4 / RC / R6 / ... di sisi R jika Anda menginginkan sesuatu yang lebih bagus.

Membungkus pointer eksternal ke dalam kelas di sisi R adalah sesuatu yang Anda dapatkan secara gratis dengan menggunakan modul 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()
*/

Keluaran:

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

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

> d1$square()
[1] 29.16

Itu juga didukung untuk menggunakan metode pabrik bukan konstruktor di C ++ tetapi dengan penggunaan yang identik di sisi 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()
*/

Keluaran:

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

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

> d1$square()
[1] 29.16

Akhirnya, RCPP_EXPOSED_CLASSberguna jika Anda ingin menggabungkan fungsi pabrik sisi R dengan Modul Rcpp, karena ini menciptakan Rcpp::asdan Rcpp::wrapekstensi yang diperlukan untuk meneruskan objek maju mundur antara R dan C ++. Pabrik dapat diekspor melalui functionseperti yang Anda lakukan atau menggunakan Atribut Rcpp, yang menurut saya lebih alami:

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

Keluaran:

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

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

> d1$square()
[1] 29.16

Mengenai pembersihan: Rcpp::XPtrModul Baik dan Rcpp mendaftarkan finalizer default yang memanggil destruktor objek. Anda juga dapat menambahkan finalizer khusus jika diperlukan.

Saya merasa sulit untuk memberikan rekomendasi untuk salah satu pendekatan ini. Mungkin yang terbaik adalah mencoba masing-masing pada beberapa contoh sederhana dan melihat apa yang menurut Anda lebih alami untuk digunakan.

Ralf Stubner
sumber
2
Barang yang sangat bagus. Anda sedang bermain di sini.
Dirk Eddelbuettel
Terima kasih. Ini sangat membantu! Saya pikir factoryadalah bagian konektor kunci saya telah hilang.
Artem Sokolov
Sebagai tindak lanjut kecil, apakah Anda kebetulan tahu apakah functionjuga mendaftar finalizer, atau hanya itu factory ?
Artem Sokolov
1
@ArtemSokolov AFAIK finalizer default yang memanggil destructor dihasilkan oleh class_<T>dan tidak tergantung pada bagaimana objek dibuat.
Ralf Stubner