Skip to content

Commit 46f0dc5

Browse files
committed
Merge remote-tracking branch 'origin/master' into bugfix/module-complete-specials
# Conflicts: # inst/tinytest/cpp/Module.cpp # inst/tinytest/test_module.R
2 parents 728c239 + 586a198 commit 46f0dc5

4 files changed

Lines changed: 82 additions & 0 deletions

File tree

‎ChangeLog‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@
7373
* inst/tinytest/cpp/Module.cpp: Add classes overloading '[['
7474
* inst/tinytest/test_module.R: Test their completions
7575

76+
* vignettes/rmd/Rcpp-modules.Rmd: Document that objects of exposed
77+
classes returned by value or reference are copied, while a returned
78+
pointer is owned and eventually deleted by R, so that returning this or
79+
another pointer the C++ side still owns deletes the object twice (#1533)
80+
* inst/tinytest/cpp/Module.cpp: Add a class counting its destructions
81+
* inst/tinytest/test_module.R: Test that R deletes a returned pointer
82+
7683
* inst/include/Rcpp/Module.h (internal::module_class_pointer): New
7784
per-library record of the class_<Class> registered for a C++ type
7885
* inst/include/Rcpp/Module.h (internal::make_new_object): Pass it to

‎inst/tinytest/cpp/Module.cpp‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,26 @@ class ModuleBracketsOnly {
194194
double value;
195195
};
196196

197+
// counts its destructions, to check who owns objects returned from C++
198+
class ModuleOwned {
199+
public:
200+
ModuleOwned(int id_) : id(id_) {}
201+
~ModuleOwned() { destroyed++; }
202+
int id;
203+
static int destroyed;
204+
};
205+
int ModuleOwned::destroyed = 0;
206+
207+
RCPP_EXPOSED_CLASS_NODECL(ModuleOwned)
208+
209+
ModuleOwned* ModuleOwned_create(int id) {
210+
return new ModuleOwned(id);
211+
}
212+
213+
int ModuleOwned_destroyed() {
214+
return ModuleOwned::destroyed;
215+
}
216+
197217
class ModuleGadget {
198218
public:
199219
ModuleGadget() {}
@@ -320,6 +340,13 @@ RCPP_MODULE(demoModule) {
320340
.field("value", &ModuleBracketsOnly::value)
321341
;
322342

343+
class_<ModuleOwned>("ModuleOwned")
344+
.field_readonly("id", &ModuleOwned::id)
345+
;
346+
347+
function("ModuleOwned_create", &ModuleOwned_create);
348+
function("ModuleOwned_destroyed", &ModuleOwned_destroyed);
349+
323350
class_<ModuleGadget>("ModuleGadget")
324351
.constructor()
325352
.method("value", static_cast<void (ModuleGadget::*)(int)>(&ModuleGadget::value))

‎inst/tinytest/test_module.R‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,15 @@ expect_equal( Rcpp:::complete( brackets ), c("total( ", "value") )
175175
expect_equal( brackets[[2L]], 2.0 )
176176
expect_equal( Rcpp:::complete( new( ModuleBracketsOnly ) ), "value" )
177177

178+
## a pointer returned from C++ is owned by the R object wrapping it, and
179+
## deleted when that object is garbage collected (#1533)
180+
destroyed <- ModuleOwned_destroyed()
181+
owned <- ModuleOwned_create( 7L )
182+
expect_equal( owned$id, 7L )
183+
rm( owned )
184+
invisible( gc() )
185+
expect_equal( ModuleOwned_destroyed(), destroyed + 1L )
186+
178187
## a body that throws after registering some of its classes and functions
179188
## leaves nothing behind, so loading the module again does not end up with
180189
## two copies of what the failed run registered

‎vignettes/rmd/Rcpp-modules.Rmd‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,45 @@ bar$handleFoo(foo)
10131013
#> Got a Foo!
10141014
```
10151015

1016+
### Returning objects of exposed classes
1017+
1018+
With `RCPP_EXPOSED_WRAP` (or `RCPP_EXPOSED_CLASS`), functions and
1019+
methods can also return objects of a class exposed by a module. How
1020+
the object is passed to \proglang{R} depends on the return type:
1021+
1022+
- An object returned by value or by reference is _copied_, using the
1023+
copy constructor of the class, and the \proglang{R} object owns the
1024+
copy.
1025+
- A pointer is not copied: the \proglang{R} object takes _ownership_
1026+
of the object it points to, and deletes it when the \proglang{R}
1027+
object is garbage collected. This is what a function returning a
1028+
newly allocated object needs, like the factories shown above, but
1029+
it means that a pointer may only be returned when nothing else owns
1030+
or deletes the object it points to. Returning `this` (e.g. to chain
1031+
method calls), a pointer to a data member or to an element of a
1032+
container, or the same pointer from more than one call results in the
1033+
object being deleted more than once, which typically crashes
1034+
\proglang{R} during garbage collection.
1035+
1036+
```{Rcpp ownership, eval=FALSE}
1037+
class Counter {
1038+
public:
1039+
Counter() : n(0) {}
1040+
1041+
// fine: R owns, and eventually deletes, the new Counter
1042+
static Counter* create() { return new Counter; }
1043+
1044+
// wrong: the R object returned by inc() would delete *this
1045+
// a second time
1046+
Counter* inc() { n++; return this; }
1047+
1048+
// fine: return void, or a copy, and chain the calls in R
1049+
void increment() { n++; }
1050+
1051+
int n;
1052+
};
1053+
```
1054+
10161055
### Full example
10171056

10181057
<!-- TODO: maybe replace this by something from wls or RcppModels ? -->

0 commit comments

Comments
 (0)