【问题标题】:How to pass a class object from an Rcpp module back into C++?如何将类对象从 Rcpp 模块传回 C++?
【发布时间】:2022-01-05 01:10:33
【问题描述】:

我有一个使用 Rcpp 模块向 R 公开的 C++ 代码库。具体来说,我使用了一种接口模式,其中我公开的类实际上是底层对象之上的一个抽象层,即实现。

我正在处理的类也相互交互,并且具有将对象的共享指针作为参数的方法。我无法找出将这些方法暴露给 R 的正确方法。

例如,这里有一些代码。 TestClass::combine 方法接受一个指向另一个 TestClass 对象的指针并使用它来处理。当我尝试编译这段代码时,当我将相应的接口方法ITestClass::combine 添加到模块时,会出现编译器错误(见下文)。

实施:

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 中调用它?

【问题讨论】:

  • 另外,如果可能的话,我不想将TestClass 暴露给R。这就是接口层的用途,R 不应该知道也不关心实现。
  • tje 对象来自哪里(这里:一个 Rcpp Modules sn-p)并不重要,重要的是 R 只允许我们使用 .Call(SEXP a, SEXP b, ....) 所以我们需要映射到 R 的 @ 987654330@。其中有整个其他小插图(R Extending)专用于它。
  • @DirkEddelbuettel 我很清楚 Rcpp 必须在 1990 年代 R API 的约束下工作。事实上,当他们抱怨时,我正在向他们解释这一点。但我仍然需要一个比我所拥有的更好的解决方案(这实际上是错误的,因为共享指针在使用 1 次后会破坏 obj2 中的对象)。
  • 成员函数double combine(ITestClass obj) 根本不适合SEXP .Call(SEXP a, SEXP b, ...) 模具直到您通过C++ 代码为ITestClass 提供包装器。编译器无法为您传递此信息,因此出现错误。
  • 对,我知道这就是为什么我在回答中将其更改为combine(SEXP)。但是如果我可以直接传递对象obj2,而不是obj2$get_object(),那就太好了。也许有办法在 C++ 中拆分obj2 的结构?

标签: r c++14 rcpp


【解决方案1】:

我找到了一个更好的解决方案,它具有combine 的首选接口,并且似乎不会遇到垃圾收集问题。

几点:

  • 因为底层 API 广泛使用共享指针,而不是在 impl 中存储 TestClass 对象,我存储了 std::shared_ptr&lt;TestClass&gt;。这是直接引用的,而不是从头开始创建新的共享 ptr(当它们被销毁时会导致 R 崩溃)。
  • 我利用了从 Rcpp 模块返回的 refclass 对象的内部结构。特别是,它有一个 .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)

【讨论】:

  • @DirkEddelbuettel 任何 cmets?
  • 对不起,我没有运行代码注释服务。我们确实有一个邮件列表,也许可以使用它?
  • 但如果它有效,它就有效。也许为 Rcpp 画廊写下来?
【解决方案2】:

编辑:查看更好的答案here

我发现了一个相当笨拙的解决方案,它涉及直接调用 R 外语 API:

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,这非常不直观
  • 我看到answers noting 认为Rcpp::XPtrstd::shared_ptr 不能很好地协同工作,因为它们都试图管理指向的内存

希望有更好的解决方案。

【讨论】:

  • 这个答案来自 2014 年,需要上下文。当您查看任何复杂的包管理对象(tiledbarrow、...)时,您会看到很多 XPtr 包装 shared_ptr 对象。请记住,归根结底...XPtr 是我们从 R 的 API 连接到外部对象的全部内容。
  • 查看新答案,任何 cmets?
  • 对不起,我没有运行代码注释服务。我们确实有一个邮件列表,也许可以使用它?
  • 但如果它有效,它就有效。也许为 Rcpp 画廊写下来?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-14
  • 2014-11-03
  • 1970-01-01
相关资源
最近更新 更多