Coder Social home page Coder Social logo

Comments (12)

Enchufa2 avatar Enchufa2 commented on May 27, 2024 2

@pierreblavy2 Dirk's advice will save you a lot of time: before trying to implement fancy things in C++, please take a look at the R internals and how the interface looks like. Some quick hints:

  • Everything in R core is a SEXP, which is a lightweight self-describing structure.
  • One of this SEXPs is the EXTPTRSXP, or external pointer. This just holds a void C pointer that could be anything.
  • So forget about unique pointers, shared pointers, and other fancy things: whenever you want to export some object to the R side, that must be a void C pointer.
  • The other piece of the puzzle that Rcpp provides to seamlessly work with external pointers is the XPtr class. This allows you not only to cast them to the proper class and use them, but also to automatically register the removal of that object when the external pointer is no longer referenced in any place (so it is similar to a shared pointer).

Now, you are looking at this issue from the perspective of your particular use case, where you have a long-lived object that takes care of everything else, so you don't want the R garbage collector to fiddle with your objects, which is fine. But that's not the only use case, and given how old Rcpp is and the number of packages using it, I'd confidently say it's not the most common case. That's why the XPtr constructor has a default bool set_delete_finalizer = true. Otherwise, we would see tons of memory leaks in the dependent packages, because developers typically create objects, export them to R and expect that R takes care of cleaning things up when they don't use them anymore.

You seem to have a special advanced use case. So do I. My recommendation would be to forget about modules, and work with XPtr directly. In my code, you'll find examples of long-lived objects that are properly garbage-collected when the reference is lost in the R session, as well as references to nodes in a linked list that need to be left alone (note the second argument set to false).

Modules are much more complicated, and have several limitations. That said, once you are familiar with XPtrs, if you feel like there's a way to improve modules, contributions are most welcome, and we can help you. :)

from rcpp.

eddelbuettel avatar eddelbuettel commented on May 27, 2024 1

Thanks for taking the time to write an issue. Rcpp is an R package using the .Call() interface using SEXP ("S Expression Pointers") so there are limits to what can be done here. If you have an idea to fundamentally improve upon this somewhat time-test solutions we'd be all ears and would welcome a PR or issue demonstrating it.

from rcpp.

eddelbuettel avatar eddelbuettel commented on May 27, 2024 1

Also, as an addendum, your example uses Rcpp Modules which are one of several ways to work with Rcpp. Modules offers a few nice features, but also have limits ... and in essence have not seen much development in close to a decade.

R offers external pointers, for Rcpp these become the XPtr object. You could play with those but because R itself is garbage collected in it is fairly easy to get entangled when mixing R object with C++ reference counted objects. But you may well have fresh ideas so I encourage to explore and express them.

from rcpp.

eddelbuettel avatar eddelbuettel commented on May 27, 2024 1

@pierreblavy2 I have been at this for 15+ years and have written quite a bit about it. We also had good luck with the overall uptake as there are over 2600 packages using Rcpp at CRAN alone, and you can search them all (!!) (plus actually any ones that once were on CRAN and are no longer) within the GitHub 'cran' organization: https://github.com/cran/

I also don't quite have the time and bandwidth now to discuss R internals here -- it is not the best place. (The r-devel and r-package-devel mailing lists may be better.) But I don't want to send you away: I was just looking to unique_ptr in the context of Rcpp::XPtr (and think it is 'nope') but there is possibly work to be done. (And I do work with zero copy memory outside of R right now for a work project.) But R is tricky, and it "takes some getting used to" and it is NOT the same as plain C++. Keep at it please, I think you will find it useful.

PS But maybe let go of Rcpp Modules. They have limitations. Look at what we do with Rcpp::Xptr; look at R itself does with external pointers. I think there may be something to be done to revisit things with nesting / sharing but I haven't had time to get into it.

from rcpp.

Enchufa2 avatar Enchufa2 commented on May 27, 2024

I don't see anything wrong about how the Rcpp module manages this. The ref method returns a const reference, but as soon as we try to return it to R, we cannot ensure that property anymore, so it is copied here to return an external pointer. The copy however does not increment the counter, so the Test class is missing a copy method which should do that:

Test(const Test& o){++count; Rcpp::Rcout<<"cstr " << count <<std::endl; }

from rcpp.

Enchufa2 avatar Enchufa2 commented on May 27, 2024

Actually, we could define a wrap_dispatch without the const specifier, and then return just the pointer to the reference. But then this would create two different S4 objects referencing the same pointer, and I don't know whether this could backfire.

And, in general, the issue is that Rcpp has no way of knowing whether a returned reference must be owned or it's already owned. So the sensible thing to do is to copy the object and set a finalizer on that copy, so that we ensure that it is garbage collected. These are the limitations, so IMO you should work with that in mind, and, in this case, define a copy constructor, or just don't export this method to R, and rely on copying the S4 object that wraps the C++ class.

from rcpp.

pierreblavy2 avatar pierreblavy2 commented on May 27, 2024

@eddelbuettel

I'm not really good for understanding the R arcana, but I'll be very happy to help and improve how object are managed. I'll be very happy to have some information about.

  • how the module magic works to expose the stuff declared in .method as callable in R? If I write some kind of wrapper for references or not owning pointer, it must be callable in R the same way the referenced or the pointed class is. I'm sorry but I was unable to find some documentation about it, so all help is welcomed.
  • how do we define a SEXP pointing to a custome object?
  • how do we define a custom destructor for a SEXP ?

I've also found some class called XPtr, that may be helpfull. I can wrap things to return a XPtr instead of a Test* but then, there is no method attached to it in R, so I cannot use it. I also don't know what XPtr does or how it manages its memory.

@Enchufa2.
Not all classes are copyable, here I don't want a Test(const Test&) function.
I may have added :
Test(const Test&)=delete;

As Test is accessed trough a reference, it's not supposed to be copied.

The fact R is garbage collected is not the main issue. The garbage collection may change WHEN the actual deletion occurs (so objects are not destroyed at the end of their scope, or when rm is called, but at some point in the future). A garbage collector actually breaks RAII, makes ressource management hell (typically when the code branches or throw exception) but it must not change HOW objects are deleted. Here we do have a double delete error, because delete is called on a not owning pointer (or on a reference), not because the actual deletion happens at an unexpected time.

from rcpp.

pierreblavy2 avatar pierreblavy2 commented on May 27, 2024

Here is the implementation of the previous idea.
The problem is that making it works requires a TON of glue code to actually expose objects for R.
If anyone can help me to find a way to write such code without repeating myself all the time, I'll greatly appreciate it.

The main idea is to wrap references in a class that actualy delete nothing, and that can be called in R with the same convension as the referenced class, so the R user doesn't see a difference when using it.

//--- test.cpp ---
#include <memory>


struct Test{
  static int count;

   Test(){++count; Rcpp::Rcout<<"cstr " << count <<std::endl; }
  ~Test(){--count; Rcpp::Rcout<<"dest " << count <<std::endl; } 
  Test(const Test&)=delete;
  Test & ref(){return *this;} 

  //other methods
  void f(){Rcpp::Rcout << "f"<<std::endl; }
};

int Test::count = 0;



struct Test_ref: std::reference_wrapper<Test> {
  typedef std::reference_wrapper<Test> ptr_type;
  using ptr_type::ptr_type;
};



#include <Rcpp.h>
using namespace Rcpp;

RCPP_EXPOSED_CLASS(Test);
RCPP_EXPOSED_CLASS(Test_ref);

Test_ref test_ref_w(Test*t){
  return  Test_ref( t->ref() ) ;
}

Test_ref Test_ref_ref_w(Test_ref*t){
  return  Test_ref(t->get().ref() ) ;
}

void Test_ref_f_w(Test_ref*t){
   t->get().f() ;
}


RCPP_MODULE(test) {
  class_<Test>("Test")
  .constructor()
  .method("ref", test_ref_w) 
  .method("f",   &Test::f ) 
;

class_<Test_ref>("Test_ref")
  .constructor<Test&>()
  .method("ref", Test_ref_ref_w  ) 
  .method("f",   Test_ref_f_w ) 
;

}
#--- test.R ---
library(Rcpp);
sourceCpp("test.cpp");

a = new(Test);  # cstr 1, as expected
b=a$ref();      # prints nothing, as expected

a; #C++ object <0x55be83c6f070> of class 'Test' <0x55be8460d400>        : OK A is of class Test
b; # C++ object <0x55be861df3e0> of class 'Test_ref' <0x55be84c87c10>  : OK B is of class Test_ref, and has a different adress

#b and a can be used with the same interface, so the user doesn't care if it's a ref or not
a$f();
b$f();

rm(b);gc();   #print nothing : OK as b is destroyed WITHOUT touching a
rm(a);gc()    # dest 0       : OK as we want to delete a

from rcpp.

pierreblavy2 avatar pierreblavy2 commented on May 27, 2024

I'm a little bit lost, but thank a lot for your help !

from rcpp.

eddelbuettel avatar eddelbuettel commented on May 27, 2024

That is par for the course. There is a lot here but you cannot do extensions modules for R / reason about how R should do things without knowing a little about R. We all started with the (somewhat hard to digest, but comprehensive) Writing R Extension (plus maybe the sibbling 'Language' and 'Internals' manuals). Many good nuggets are in Section 5 and below.

from rcpp.

pierreblavy2 avatar pierreblavy2 commented on May 27, 2024

Thank a lot !
I'll try to follow your advice. I still have things to learn, but it's getting clearer with the link you've posted.

from rcpp.

eddelbuettel avatar eddelbuettel commented on May 27, 2024

Also: "Simple works." I keep forgetting where I did this but I do have a package (or more) where I casually construct a C++ class as I like, use it as a singleton (hey, we R know is not multithread) accessible from a static pointer and then use Rcpp for helpers functions to

  • allocate / init / setup
  • setter and getter as I need it
  • wind it down

(though I should add that by now I would probably use an XPtr instead .... That design is from younger days. Then again "simple works", and quite well at times.)

The R 'foreign function interface' is robust and time tested. There are things it does, and things is doesn't. But it is the only interface we have so we play the hand we've been dealt, not the one we dream we had.

from rcpp.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.