如何将 class 对象从 Rcpp 模块传回 C++?
How to pass a class object from an Rcpp module back into C++?
我有一个 C++ 代码库,我使用 Rcpp 模块将其暴露给 R。具体来说,我使用了一种接口模式,其中我公开的 class(es) 实际上是底层对象之上的抽象层,即实现。
我正在处理的 class(es) 也相互交互,并且具有将对象共享指针作为参数的方法。我无法找到将这些方法公开给 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 调用它?
编辑:查看更好的答案
我发现了一个相当笨拙的解决方案,涉及直接调用 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::XPtr
和 std::shared_ptr
不能很好地协同工作,因为它们都试图管理指向 的内存
希望有更好的解决方案。
我找到了一个更好的解决方案,它具有 combine
的首选接口并且似乎 运行 没有遇到垃圾收集问题。
几点:
- 因为底层 API 广泛使用共享指针,而不是在
impl
中存储一个 TestClass
对象,我存储一个 std::shared_ptr<TestClass>
。这是直接引用的,而不是从头开始创建新的共享 ptrs(当它们被销毁时会导致 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)
我有一个 C++ 代码库,我使用 Rcpp 模块将其暴露给 R。具体来说,我使用了一种接口模式,其中我公开的 class(es) 实际上是底层对象之上的抽象层,即实现。
我正在处理的 class(es) 也相互交互,并且具有将对象共享指针作为参数的方法。我无法找到将这些方法公开给 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 调用它?
编辑:查看更好的答案
我发现了一个相当笨拙的解决方案,涉及直接调用 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::XPtr
和std::shared_ptr
不能很好地协同工作,因为它们都试图管理指向 的内存
希望有更好的解决方案。
我找到了一个更好的解决方案,它具有 combine
的首选接口并且似乎 运行 没有遇到垃圾收集问题。
几点:
- 因为底层 API 广泛使用共享指针,而不是在
impl
中存储一个TestClass
对象,我存储一个std::shared_ptr<TestClass>
。这是直接引用的,而不是从头开始创建新的共享 ptrs(当它们被销毁时会导致 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)