Reputation: 13691
Consider 1) a custom class with a potentially large memory print, and 2) a top-level function that performs some pre-processing, then creates and returns a new object of our custom class. To avoid unnecessary copying by value, the function allocates the object and returns a pointer to it instead.
Based on a previous discussion, it seems that the proper way to return a pointer to a newly-created object is to wrap it with Rcpp::XPtr<>
. However, R then sees it effectively as externalptr
, and I am struggling to find the proper way to cast it with the modern RCPP_EXPOSED_CLASS
and RCPP_MODULE
way of doing things.
The alternative is to return the raw pointer. But then I'm not 100% certain that the object memory gets properly cleaned up. I ran valgrind
to test for memory leaks, and it didn't find any. However, who does the clean up? R?
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")
;
}
In 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
My question is whether Rcpp::Xptr<>
is the proper way of returning pointers, and if so, how do I get R to see the result as Double
, not externalptr
? Alternatively, if returning a raw pointer doesn't cause memory issues, who cleans up the object that the function creates?
Upvotes: 11
Views: 1813
Reputation: 26823
I think it makes sense to look at the different approaches separately. This makes the distinction clearer. Note that this is quite similar to the discussion in the Rcpp Modules vignette.
When using Rcpp::XPtr
you have your class and provide exported C++ functions for every method you want to expose:
#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)
*/
Output:
> Rcpp::sourceCpp('59384221/xptr.cpp')
> (d2 <- makeDouble(5.4))
<pointer: 0x560366699b50>
> squareDouble(d2)
[1] 29.16
Note that in R the object is only a "pointer". You could add an S4/RC/R6/... class on the R side if you want something nicer.
Wrapping the external pointer into a class on the R side is something you get for free by using Rcpp modules:
#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()
*/
Output:
> Rcpp::sourceCpp('59384221/modules.cpp')
> (d1 <- new(Double, 5.4))
C++ object <0x560366452eb0> of class 'Double' <0x56036480f320>
> d1$square()
[1] 29.16
It is also supported to use a factory method instead of a constructor in C++ but with identical usage on the R side:
#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()
*/
Output:
> Rcpp::sourceCpp('59384221/modules-factory.cpp')
> (d1 <- new(Double, 5.4))
C++ object <0x5603665aab80> of class 'Double' <0x5603666eaae0>
> d1$square()
[1] 29.16
Finally, RCPP_EXPOSED_CLASS
comes in handy if you want to combine an R side factory function with Rcpp Modules, since this creates the Rcpp::as
and Rcpp::wrap
extensions needed to pass objects back an forth between R and C++. The factory could be exported via function
as you did or using Rcpp Attributes, which I find more natural:
#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()
*/
Output:
> Rcpp::sourceCpp('59384221/modules-expose.cpp')
> (d1 <- makeDouble(5.4))
C++ object <0x560366ebee10> of class 'Double' <0x560363d5f440>
> d1$square()
[1] 29.16
Concerning clean-up: Both Rcpp::XPtr
and Rcpp Modules register a default finalizer that calls the object's destructor. You can also add a custom finalizer if needed.
I find it difficult to give a recommendation for one of these approaches. Maybe it is best to try each of them on some simple example and see what you find more natural to use.
Upvotes: 17