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?
We can pass pointers to the function as well as return pointer from a function. But it is not recommended to return the address of a local variable outside the function as it goes out of scope after function returns.
Return Function Pointer From Function: To return a function pointer from a function, the return type of function should be a pointer to another function. But the compiler doesn't accept such a return type for a function, so we need to define a type that represents that particular function pointer.
Using a pointer-to-member-function to call a function Calling the member function on an object using a pointer-to-member-function result = (object. *pointer_name)(arguments); or calling with a pointer to the object result = (object_ptr->*pointer_name)(arguments);
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With