Правильный способ вернуть указатель на `новый` объект из функции Rcpp

9

Рассмотрим 1) пользовательский класс с потенциально большим объемом памяти и 2) функцию верхнего уровня, которая выполняет некоторую предварительную обработку, а затем создает и возвращает новый объект нашего пользовательского класса. Чтобы избежать ненужного копирования по значению, функция выделяет объект и вместо него возвращает указатель.

Исходя из предыдущего обсуждения , кажется, что правильный способ вернуть указатель на вновь созданный объект - это обернуть его Rcpp::XPtr<>. Тем не менее, R тогда видит это эффективно externalptr, и я изо всех сил пытаюсь найти правильный способ придать ему современность RCPP_EXPOSED_CLASSи RCPP_MODULEспособ делать вещи.

Альтернатива - вернуть необработанный указатель. Но тогда я не уверен на 100%, что память объекта правильно очищена. Я побежал, valgrindчтобы проверить на утечки памяти, и он не нашел. Однако кто занимается уборкой? Р?

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")
    ;
}

В 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

Мой вопрос заключается в том, является ли 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>и не зависит от того, как создается объект.
Ральф