Cách thích hợp để trả về một con trỏ tới một đối tượng `new` từ hàm Rcpp


9

Xem xét 1) một lớp tùy chỉnh có khả năng in bộ nhớ lớn và 2) hàm cấp cao nhất thực hiện một số tiền xử lý, sau đó tạo và trả về một đối tượng mới của lớp tùy chỉnh của chúng tôi. Để tránh sao chép không cần thiết theo giá trị, hàm phân bổ đối tượng và trả về một con trỏ cho nó.

Dựa trên một cuộc thảo luận trước đó , có vẻ như cách thích hợp để trả lại một con trỏ cho một đối tượng mới được tạo là bọc nó lại Rcpp::XPtr<>. Tuy nhiên, R sau đó thấy nó hiệu quả như externalptr, và tôi đang vật lộn để tìm ra cách thức phù hợp để đúc nó với cách làm hiện đại RCPP_EXPOSED_CLASSRCPP_MODULEcách làm.

Thay thế là trả về con trỏ thô. Nhưng sau đó tôi không chắc chắn 100% rằng bộ nhớ đối tượng được dọn sạch đúng cách. Tôi đã chạy valgrindđể kiểm tra rò rỉ bộ nhớ và nó không tìm thấy. Tuy nhiên, ai làm sạch? 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")
    ;
}

Trong 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

Câu hỏi của tôi là liệu Rcpp::Xptr<>cách trả về con trỏ thích hợp, và nếu vậy, làm thế nào để tôi có được R để xem kết quả như thế nào Double, không externalptr? Ngoài ra, nếu trả về một con trỏ thô không gây ra vấn đề về bộ nhớ, ai sẽ dọn sạch đối tượng mà hàm tạo ra?


Có, bạn có thể muốn Rcpp::XPtrtạo một con trỏ bên ngoài từ mã C ++. Và bạn muốn bỏ nó làm double *hoặc bất cứ trọng tải của bạn là gì. Cần có những ví dụ ở đây, tại Thư viện, tại GitHub ... Có lẽ với một tìm kiếm có động lực, bạn có thể tìm thấy thứ gì đó đủ gần?
Dirk Eddelbuettel

Xin chào @DirkEddelbuettel Các diễn viên thực sự cần phải có CustomClass*. Ứng dụng thực tế là một cấu trúc dữ liệu tùy chỉnh không có R tương đương và tất cả các tương tác được thực hiện thông qua chức năng được hiển thị bởi RCPP_MODULE. Trận đấu gần nhất mà tìm kiếm động lực của tôi tìm thấy là một bài đăng từ 7 năm trước , trong đó dường như tôi cần xác định một trình template <> CustomClass* as()chuyển đổi. Tuy nhiên, tôi không rõ ràng về cách nó nên tương tác RCPP_MODULERCPP_EXPOSED_CLASS, đặc biệt là vì tôi nghĩ cái sau đã được xác định wrap()as().
Artem Sokolov

Bài đăng của Romain từ cùng một chủ đề cũng rất hữu ích, nhưng thật không may, nó làm nổi bật việc sử dụng các đối tượng trực tiếp, thay vì xử lý các con trỏ.
Artem Sokolov

1
Tôi biết tôi đã làm những thứ tương tự nhưng bây giờ tôi không chắc ví dụ nào tốt nhất này. Bạn có thể thiết lập rõ ràng một đối tượng 'singleton' và bọc dưới dạng một mô-đun (RcppRedis); Tôi nghĩ rằng tôi đã làm những gì bạn mô tả ở một hoặc hai công việc trước đây nhưng bây giờ tôi không thể nghĩ ra một ví dụ công khai tốt. Sau đó, một lần nữa - các trình bao bọc cơ sở dữ liệu khác nhau và gói truy cập làm điều đó. Không phải là chủ đề nhỏ nhất, vì vậy có thể bắt đầu với việc thực hiện đồ chơi / giả và xây dựng từ đó?
Dirk Eddelbuettel

Sử dụng RCPP_EXPOSED_CLASSRCPP_MODULEthực sự là cách để làm điều đó? Tôi chưa bao giờ sử dụng hoặc thấy điều đó trước đây.
F. Privé

Câu trả lời:


7

Tôi nghĩ rằng nó có ý nghĩa để xem xét các cách tiếp cận khác nhau một cách riêng biệt. Điều này làm cho sự khác biệt rõ ràng hơn. Lưu ý rằng điều này khá giống với cuộc thảo luận trong họa tiết Rcpp Modules.

Khi sử dụng, Rcpp::XPtrbạn có lớp của mình và cung cấp các hàm C ++ đã xuất cho mọi phương thức bạn muốn trưng ra:

#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)
*/

Đầu ra:

> Rcpp::sourceCpp('59384221/xptr.cpp')

> (d2 <- makeDouble(5.4))
<pointer: 0x560366699b50>

> squareDouble(d2)
[1] 29.16

Lưu ý rằng trong R đối tượng chỉ là một "con trỏ". Bạn có thể thêm một lớp S4 / RC / R6 / ... ở bên R nếu bạn muốn thứ gì đó đẹp hơn.

Gói con trỏ bên ngoài vào một lớp ở bên R là thứ bạn nhận được miễn phí bằng cách sử dụng các mô-đun Rcpp:

#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()
*/

Đầu ra:

> Rcpp::sourceCpp('59384221/modules.cpp')

> (d1 <- new(Double, 5.4))
C++ object <0x560366452eb0> of class 'Double' <0x56036480f320>

> d1$square()
[1] 29.16

Nó cũng được hỗ trợ để sử dụng phương thức xuất xưởng thay vì hàm tạo trong C ++ nhưng với cách sử dụng giống hệt nhau ở phía 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()
*/

Đầu ra:

> Rcpp::sourceCpp('59384221/modules-factory.cpp')

> (d1 <- new(Double, 5.4))
C++ object <0x5603665aab80> of class 'Double' <0x5603666eaae0>

> d1$square()
[1] 29.16

Cuối cùng, RCPP_EXPOSED_CLASSrất hữu ích nếu bạn muốn kết hợp chức năng nhà máy bên R với Mô-đun Rcpp, vì điều này tạo ra các phần mở rộng Rcpp::asRcpp::wrapcần thiết để chuyển các đối tượng qua lại giữa R và C ++. Nhà máy có thể được xuất qua functionnhư bạn đã làm hoặc sử dụng Thuộc tính Rcpp, điều mà tôi thấy tự nhiên hơn:

#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()
*/

Đầu ra:

> Rcpp::sourceCpp('59384221/modules-expose.cpp')

> (d1 <- makeDouble(5.4))
C++ object <0x560366ebee10> of class 'Double' <0x560363d5f440>

> d1$square()
[1] 29.16

Liên quan đến dọn dẹp: Cả Rcpp::XPtrMô-đun và Rcpp đều đăng ký một bộ hoàn thiện mặc định gọi hàm hủy của đối tượng. Bạn cũng có thể thêm một bộ hoàn thiện tùy chỉnh nếu cần.

Tôi thấy khó đưa ra một khuyến nghị cho một trong những cách tiếp cận này. Có lẽ tốt nhất là thử từng cái trong số chúng trên một ví dụ đơn giản và xem những gì bạn thấy tự nhiên hơn để sử dụng.


2
Những thứ rất đẹp. Bạn đang ở đây
Dirk Eddelbuettel

Cảm ơn bạn. Điều này là vô cùng hữu ích! Tôi nghĩ factorylà phần kết nối quan trọng tôi đã bị mất.
Artem Sokolov

Theo dõi nhỏ, bạn có biết nếu functioncũng đăng ký một bộ hoàn thiện, hay chỉ là factory ?
Artem Sokolov

1
@ArtemSokolov AFAIK trình hoàn thiện mặc định gọi hàm hủy được tạo bởi class_<T>và độc lập với cách tạo đối tượng.
Ralf Stubner
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.