Как передать объект класса из модуля Rcpp обратно в С++?

У меня есть кодовая база C++, которую я открываю для R с помощью модулей Rcpp. В частности, я использую шаблон интерфейса, в котором классы, которые я предоставляю, на самом деле являются уровнем абстракции поверх базового объекта, который является реализацией.

Классы, с которыми я имею дело, также взаимодействуют друг с другом и имеют методы, которые принимают в качестве аргументов общие указатели на объекты. У меня возникли проблемы с определением правильного способа предоставления этих методов R.

Например, вот некоторый код. TestClass::combineметод принимает указатель на другой TestClassобъект и делает с ним что-то. Когда я пытаюсь скомпилировать этот код, я получаю ошибки компилятора (см. ниже) при добавлении в модуль соответствующего метода интерфейса.

Реализация:

      class TestClass
{
public:
    TestClass(int const& n, double const& x)
        : n(n), x(x)
    {}

    const double get_x() {
        return x;
    }

    double combine(std::shared_ptr<TestClass> obj) {
        return x + obj->get_x();
    }

protected:
    int n;
    double x;
};

Интерфейс:

      //' @export ITestClass
class ITestClass
{
public:
    ITestClass(int const& in_n, double const& in_x)
        : impl(in_n, in_x)
    {}

    double get_x() {
        return impl.get_x();
    }

    double combine(ITestClass obj) {
        return impl.combine(obj.get_object_ptr());
    }

    std::shared_ptr<TestClass> get_object_ptr() {
        std::shared_ptr<TestClass> ptr(&impl);
        return ptr;
    }

private:
    TestClass impl;
};

RCPP_MODULE(RTestClassModule)
{
    class_<ITestClass>("ITestClass")
        .constructor<int, double>()
        .method("get_x", &ITestClass::get_x, "get_x")
        .method("combine", &ITestClass::combine, "combine"); // this line errors out
}

Пример ошибок, которые я получаю:

          In file included from C:/Rlib/Rcpp/include/Rcpp/as.h:25,
                    from C:/Rlib/Rcpp/include/RcppCommon.h:168,
                    from C:/Rlib/Rcpp/include/Rcpp.h:27,
                    from interface1.cpp:2:
   C:/Rlib/Rcpp/include/Rcpp/internal/Exporter.h: In instantiation of 'Rcpp::traits::Exporter<T>::Exporter(SEXP) [with T = testpkg::ITestClass; SEXP = SEXPREC*]':
   C:/Rlib/Rcpp/include/Rcpp/as.h:87:41:   required from 'T Rcpp::internal::as(SEXP, Rcpp::traits::r_type_generic_tag) [with T = testpkg::ITestClass; SEXP = SEXPREC*]'
   C:/Rlib/Rcpp/include/Rcpp/as.h:152:31:   required from 'T Rcpp::as(SEXP) [with T = testpkg::ITestClass; SEXP = SEXPREC*]'
   C:/Rlib/Rcpp/include/Rcpp/InputParameter.h:34:43:   required from 'Rcpp::InputParameter<T>::operator T() [with T = testpkg::ITestClass]'
   C:/Rlib/Rcpp/include/Rcpp/module/Module_generated_CppMethod.h:111:69:   required from 'SEXPREC* Rcpp::CppMethod1<Class, RESULT_TYPE, U0>::operator()(Class*, SEXPREC**) [with Class = testpkg::ITestClass; RESULT_TYPE = double; U0 = testpkg::ITestClass; SEXP = SEXPREC*]'
   C:/Rlib/Rcpp/include/Rcpp/module/Module_generated_CppMethod.h:109:10:   required from here
   C:/Rlib/Rcpp/include/Rcpp/internal/Exporter.h:31:31: error: no matching function for 
call to 'testpkg::ITestClass::ITestClass(SEXPREC*&)'
          Exporter( SEXP x ) : t(x){}
                                  ^
   interface1.cpp:17:5: note: candidate: 'testpkg::ITestClass::ITestClass(SEXP, const int&, const double&)'
        ITestClass(SEXP in_date, int const& in_n, double const& in_x)
        ^~~~~~~~~~
   interface1.cpp:17:5: note:   candidate expects 3 arguments, 1 provided
   interface1.cpp:14:7: note: candidate: 'constexpr testpkg::ITestClass::ITestClass(const testpkg::ITestClass&)'
    class ITestClass
          ^~~~~~~~~~
   interface1.cpp:14:7: note:   no known conversion for argument 1 from 'SEXP' {aka 'SEXPREC*'} to 'const testpkg::ITestClass&'
   interface1.cpp:14:7: note: candidate: 'constexpr testpkg::ITestClass::ITestClass(testpkg::ITestClass&&)'
   interface1.cpp:14:7: note:   no known conversion for argument 1 from 'SEXP' {aka 'SEXPREC*'} to 'testpkg::ITestClass&&'

Как я могу определить ITestClass::combineчтобы его можно было вызывать из R?

2 ответа

Я нашел лучшее решение, которое имеет предпочтительный интерфейс для combineи, кажется, не сталкивается с проблемами со сборкой мусора.

Несколько моментов:

  • Поскольку базовый API активно работает с общими указателями, а не хранит TestClassобъект в , я храню std::shared_ptr<TestClass>. На это ссылаются напрямую, а не создают новые общие указатели с нуля (которые приводят к сбою R при их уничтожении).
  • Я использую внутреннюю структуру возвращенного объекта refclass из модуля Rcpp. В частности, он имеет .pointerчлен, который является указателем на базовый объект C++. Поэтому я могу разыменовать это, чтобы получить implчлен.

Новый интерфейс:

      //' @export ITestClass2
class ITestClass2
{
public:
    ITestClass2(int const& in_n, double const& in_x)
        : impl(in_n, in_x))
    {}

    double get_x()
    {
        return impl->get_x();
    }

    double combine(Environment obj)
    {
        SEXP objptr = obj[".pointer"];
        ITestClass2* ptr = (ITestClass2*) R_ExternalPtrAddr(objptr);
        return impl->combine(ptr->get_object_ptr());
    }

// this doesn't need to be seen from R
protected:
    std::shared_ptr<TestClass> get_object_ptr()
    {
        return impl;
    }

private:
    std::shared_ptr<TestClass> impl;
};

RCPP_MODULE(RTestClassModule2)
{
    class_<ITestClass2>("ITestClass2")
        .constructor<int, double>()
        .method("get_x", &ITestClass2::get_x, "get_x")
        .method("combine", &ITestClass2::combine, "combine")
    ;
}

Вызовите это в R следующим образом:

      obj <- new(ITestClass2, 1, pi)
obj2 <- new(ITestClass2, 2, exp(1))
obj$combine(obj2)

Изменить: см. лучший ответ здесь

Я нашел довольно неуклюжее решение, которое включает прямой вызов API иностранного языка R:

      class ITestClass {

    ...

    double combine(SEXP obj) {
        TestClass* ptr = (TestClass*) R_ExternalPtrAddr(obj);
        std::shared_ptr<TestClass> sptr(ptr);
        return impl.combine(sptr);
    }

    Rcpp::XPtr<TestClass> get_object() {
        return Rcpp::XPtr<TestClass>(&impl);
    }
}
      obj1 <- new(ITestClass, 1, pi)
obj2 <- new(ITestClass, 2, -0.1)

obj1$combine(obj2$get_object())
# [1] 3.041593

Это не очень хорошо по нескольким причинам:

  • На стороне R я должен пройти obj$get_object()в качестве аргумента combine, что крайне неинтуитивно
  • Я видел ответы, отмечая , что Rcpp::XPtrа также std::shared_ptrне работают вместе, так как они оба пытаются управлять памятью, на которую указывает

Надеюсь, есть лучшее решение.

Другие вопросы по тегам