Правильний спосіб повернути вказівник на "новий" об'єкт з функції Rcpp


9

Розглянемо 1) спеціальний клас з потенційно великим друком пам’яті та 2) функцію верхнього рівня, яка виконує деяку попередню обробку, потім створює та повертає новий об’єкт нашого спеціального класу. Щоб уникнути зайвого копіювання за значенням, функція розподіляє об'єкт і повертає на нього вказівник.

Виходячи з попередньої дискусії , здається, що правильний спосіб повернути вказівник на новостворений об’єкт - це обернути його Rcpp::XPtr<>. Однак R потім розглядає це ефективно як externalptr, і я намагаюся знайти належний спосіб викласти це за допомогою сучасного RCPP_EXPOSED_CLASSі RCPP_MODULEспособу здійснення справ.

Альтернатива - повернути необроблений покажчик. Але тоді я не на 100% впевнений у тому, що пам'ять об'єкта правильно очищається. Я побіг valgrindперевірити на предмет витоку пам’яті, і його не знайшли. Однак хто займається прибиранням? 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")
    ;
}

В Р

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

Моє запитання полягає в тому, чи Rcpp::Xptr<>є правильним способом повернення покажчиків, і якщо так, то як я можу отримати R, щоб результат бачив як Double, ні externalptr? Крім того, якщо повернення необробленого покажчика не викликає проблем з пам'яттю, хто очищає об'єкт, який створює функція?


Так, ймовірно, ви хочете Rcpp::XPtrстворити зовнішній вказівник з коду C ++. І ви хочете, щоб це було зроблено, double *або що б не було вашим корисним навантаженням. Тут повинні бути приклади, в Галереї, на GitHub ... Може, за допомогою вмотивованого пошуку ви зможете знайти щось досить близьке?
Дірк Еддельбуеттель

Привіт @DirkEddelbuettel Акторський склад справді повинен бути CustomClass*. Справжня програма - це власна структура даних без R-еквівалента, і всі взаємодії здійснюються через функціонал, що піддається RCPP_MODULE. Найбільш близьким моїм мотивованим пошуком було повідомлення від 7 років тому , де, здається, мені потрібно визначити template <> CustomClass* as()перетворювач. Однак мені незрозуміло, як це має взаємодіяти, RCPP_MODULEі RCPP_EXPOSED_CLASSтим більше, що я вважав, що остання вже визначила wrap()і as().
Артем Соколов

Допис Ромена з цієї ж теми також дуже корисний, але, на жаль, він підкреслює використання об'єктів безпосередньо, а не обробку покажчиків.
Артем Соколов

1
Я знаю, що робив подібні речі, але зараз я не впевнений, що це найкращий приклад. Ви можете чітко налаштувати об'єкт 'singleton' і обернути його як модуль (RcppRedis); Я думаю, що я зробив те, що ви описуєте на попередній роботі або на двох, але зараз я не можу придумати хорошого громадського прикладу. Потім знову - різні обгортки бази даних та пакет доступу роблять це. Чи не найменша тема, тож, можливо, почніть з реалізації іграшки / макету та побудувати звідти?
Дірк Еддельбюттель

Використання RCPP_EXPOSED_CLASSта RCPP_MODULEчи справді спосіб це зробити? Я ніколи цього не використовував і не бачив.
F. Privé

Відповіді:


7

Я думаю, що є сенс дивитись на різні підходи окремо. Це робить розрізнення чіткішим. Зауважте, що це досить схоже на обговорення у віньетці модулів Rcpp.

Під час використання Rcpp::XPtrви маєте свій клас та надаєте експортовані функції C ++ для кожного методу, який ви хочете викрити:

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

Вихід:

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

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

> squareDouble(d2)
[1] 29.16

Зауважимо, що в R об'єктом є лише "покажчик". Ви можете додати клас S4 / RC / R6 / ... на стороні R, якщо хочете чогось приємного.

Встановлення зовнішнього вказівника в клас на стороні R - це те, що ви отримуєте безкоштовно, використовуючи модулі 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()
*/

Вихід:

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

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

> d1$square()
[1] 29.16

Також підтримується використання заводського методу замість конструктора на C ++, але з однаковим використанням на стороні 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()
*/

Вихід:

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

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

> d1$square()
[1] 29.16

Нарешті, це RCPP_EXPOSED_CLASSстане в нагоді, якщо ви хочете поєднати заводську функцію R з модулями Rcpp, оскільки це створює Rcpp::asі Rcpp::wrapрозширення, необхідні для передачі об'єктів вперед між R і C ++. functionФабрику можна експортувати за допомогою атрибутів Rcpp, що я вважаю більш природним:

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

Вихід:

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

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

> d1$square()
[1] 29.16

Щодо очищення: Обидва Rcpp::XPtrта модулі Rcpp реєструють фіналізатор за замовчуванням, який викликає деструктор об'єкта. Ви також можете додати спеціальний фіналізатор, якщо потрібно.

Мені важко дати рекомендацію щодо одного з цих підходів. Можливо, найкраще спробувати кожен з них на якомусь простому прикладі і подивитися, що вам здається більш природним.


2
Дуже приємні речі. Ви тут на рулоні.
Дірк Еддельбуеттель

Дякую. Це надзвичайно корисно! Я думаю, що factoryце ключовий штекер, який я бракував.
Артем Соколов

Як невелике спостереження, чи знаєте ви, чи functionреєструє фіналізатор, чи це лише factory ?
Артем Соколов

1
@ArtemSokolov AFAIK фіналізатор за замовчуванням, який викликає деструктор, генерується class_<T>і не залежить від того, як створюється об'єкт.
Ральф Стубнер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.