从Rcpp函数返回指向`new`对象的指针的正确方法


9

考虑1)具有潜在的大内存打印的自定义类,以及2)执行一些预处理,然后创建并返回我们的自定义类的新对象的顶级函数。为避免不必要的值复制,该函数分配对象并返回指向它的指针。

根据前面的讨论,似乎将指针返回到新创建的对象的正确方法是将其包装为Rcpp::XPtr<>。然而,R则认为它有效的externalptr,而我在努力寻找与现代投它的正确方法RCPP_EXPOSED_CLASSRCPP_MODULE做事的方式。

另一种方法是返回原始指针。但是,我不能100%地确定对象内存是否已正确清理。我跑去valgrind测试内存泄漏,但没有发现任何泄漏。但是,谁清理呢?R?

测试文件

#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的Gallery上...也许通过积极的搜索,您可以找到足够接近的东西?
Dirk Eddelbuettel

嗨@DirkEddelbuettel演员表确实需要这样做CustomClass*。真正的应用程序是一个自定义数据结构,没有R等效项,所有交互都通过公开的功能完成RCPP_MODULE。我最有动机的搜索发现最接近的匹配是7年前帖子,看来我需要定义一个template <> CustomClass* as()转换器。但是,我不清楚它应如何与RCPP_MODULE和交互RCPP_EXPOSED_CLASS,尤其是因为我认为后者已经定义了wrap()as()
Artem Sokolov

Romain在同一线程中的帖子也非常有帮助,但是不幸的是,它直接强调了对象的使用,而不是指针的处理。
Artem Sokolov

1
我知道我做过类似的事情,但是现在我不确定这是什么最好的例子。您可以清楚地设置一个“单个”对象并包装为一个模块(RcppRedis);我认为我已经完成了您之前或两份工作中所描述的工作,但现在我想不出一个很好的公开榜样。再说一遍-各种数据库包装程序和访问程序包都执行此操作。不是最小的主题,所以也许从玩具/模拟实现开始并从那里构建?
Dirk Eddelbuettel

使用RCPP_EXPOSED_CLASSRCPP_MODULE真的是这样做的方法吗?我以前从未使用过或看到过。
F.Privé19年

Answers:


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中,对象只是“指针”。如果您想要更好的东西,可以在R侧添加S4 / RC / R6 / ...类。

使用Rcpp模块可以免费将外部指针包装到R侧的类中:

#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::asRcpp::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
很好的东西。您在这里。
Dirk Eddelbuettel

谢谢。这非常有帮助!我认为这factory是我一直缺少的关键连接器。
Artem Sokolov

作为一个较小的后续步骤,您是否偶然知道是否function还注册了终结器,或者仅仅是factory
Artem Sokolov

1
@ArtemSokolov AFAIK调用析构函数的默认终结器由其生成,class_<T>并且与创建对象的方式无关。
拉尔夫·斯塔伯纳
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.