Repository navigation
each Module() call re-runs the module body, duplicating constructors and methods #1527
Description
Every call to Module("name", PACKAGE = ...) runs the module's RCPP_MODULE body again, and the body appends to a static Rcpp::Module object that persists for the life of the DLL. So each call adds another copy of every constructor and every method overload to the module's classes. Fields and functions are kept in maps and are not duplicated, but the replaced CppFunction and property objects leak.
Module() calls the boot function (.Call(symbol) in R/Module.R) whenever it is given a module name, since it starts from a fresh, uninitialized Module object. The boot function generated by RCPP_MODULE does no bookkeeping of its own (inst/include/Rcpp/Module.h), so it re-executes _rcpp_module_<name>_init() every time. Loading a module more than once is common: loadModule() in a package plus an explicit Module("name", PACKAGE = "pkg") call from user code, repeated Module(..., getDynLib(fx)) calls as in the Rcpp-modules vignette, etc.
Practical effects:
show()of a class lists each constructor and method N times (see below).- Overload resolution walks a list that keeps growing; the first match still wins, so calls behave the same, but every load leaks the duplicated metadata.
- For a class built with
.derives<Parent>(), the parent's (already duplicated) methods are copied in again on every load.
Reproducible example
repro.cpp:
#include <Rcpp.h>
using namespace Rcpp;
class Counter {
public:
Counter() : n(0) {}
void inc() { n++; }
int n;
};
RCPP_MODULE(counter_module) {
class_<Counter>("Counter")
.constructor()
.method("inc", &Counter::inc)
.field("n", &Counter::n);
}repro.R:
Rcpp::sourceCpp("repro.cpp") # loads the module once
# the DLL sourceCpp() just built and loaded
dll <- getLoadedDLLs()[[length(getLoadedDLLs())]]
for (i in 1:3) {
mod <- Module("counter_module", PACKAGE = dll, mustStart = TRUE)
cat(sprintf(
"Module() call %d: %d constructor(s), %d overload(s) of inc()\n",
i,
length(mod$Counter@constructors),
length(mod$Counter@methods$inc@nargs)
))
}
show(mod$Counter)Output with R 4.6.1 (macOS 27, Rcpp 1.1.2.6 / current master):
Module() call 1: 2 constructor(s), 2 overload(s) of inc()
Module() call 2: 3 constructor(s), 3 overload(s) of inc()
Module() call 3: 4 constructor(s), 4 overload(s) of inc()
C++ class 'Counter' <0x793a9d4100>
Constructors:
Counter()
Counter()
Counter()
Counter()
Fields:
int n
Methods:
void inc()
void inc()
void inc()
void inc()
The class should have one constructor and one inc() overload no matter how many times the module is loaded.
I'll follow up with a PR that makes the boot function run the module body only once per DLL (a static flag next to the static Rcpp::Module, set once the body completes) and simply return the module pointer on later calls. It touches the same macro as #1526, so I'll stack it on that PR.
Activity