For a module class without a default constructor, calling new(Class) with no arguments deliberately creates an "uninitialized" object: the generated initialize method falls back to cpp_object_dummy() (R/Module.R), which stores a dummy pointer and notes
## like initializer but a dummy for the case of no default
## constructor. Will throw an error if the object is used.
The C++ side implements that contract in src/module.cpp:
#define CHECK_DUMMY_OBJ(p) if (p == rcpp_dummy_pointer) throw Rcpp::not_initialized()
But the three method-invocation entry points that use it -- CppMethod__invoke, CppMethod__invoke_void, and CppMethod__invoke_notvoid -- are plain .External functions with no BEGIN_RCPP/END_RCPP. The Rcpp::not_initialized exception escapes the entry point into R's C evaluator, where there is no handler, and the process calls std::terminate(): the entire R session aborts. tryCatch() on the R side never gets a chance. The intended behavior is clearly a regular R error ("C++ object not initialized. (Missing default constructor?)").
The XPtr constructor validation throws (not_compatible, null-pointer checks) in the same functions and in class__newInstance can escape the same way, though those require calling the internal entry points with malformed arguments; the dummy-object path is reachable from ordinary, documented usage.
Reproducible example
repro.cpp:
#include <Rcpp.h>
using namespace Rcpp;
class Widget {
public:
Widget(double v_) : v(v_) {}
double value() {
return v;
}
private:
double v;
};
RCPP_MODULE(widget_module) {
class_<Widget>("Widget")
.constructor<double>()
.method("value", &Widget::value);
}
repro.R:
Rcpp::sourceCpp("repro.cpp")
# Widget has no default constructor, so new(Widget) with no arguments
# takes the documented cpp_object_dummy() path
w <- methods::new(Widget)
cat("uninitialized object created\n")
# intended: an R error, "C++ object not initialized. (Missing default
# constructor?)"; actual: the C++ exception escapes .External uncaught
res <- tryCatch(w$value(), error = function(e) conditionMessage(e))
cat("caught R error:", res, "\n")
Output with R 4.6.1 (macOS, Rcpp 1.1.2 -- src/module.cpp is unchanged on current master):
uninitialized object created
libc++abi: terminating due to uncaught exception of type Rcpp::not_initialized: C++ object not initialized. (Missing default constructor?)
Abort trap: 6
Rscript exits with code 134 (SIGABRT); the final cat() is never reached.
I'll follow up with a PR that wraps these entry points in BEGIN_RCPP/END_RCPP, so the exception is converted to the intended R error. (This also lets the XPtr locals in those frames destruct properly on the error path instead of being skipped by the exception's escape.)
For a module class without a default constructor, calling
new(Class)with no arguments deliberately creates an "uninitialized" object: the generatedinitializemethod falls back tocpp_object_dummy()(R/Module.R), which stores a dummy pointer and notesThe C++ side implements that contract in
src/module.cpp:But the three method-invocation entry points that use it --
CppMethod__invoke,CppMethod__invoke_void, andCppMethod__invoke_notvoid-- are plain.Externalfunctions with noBEGIN_RCPP/END_RCPP. TheRcpp::not_initializedexception escapes the entry point into R's C evaluator, where there is no handler, and the process callsstd::terminate(): the entire R session aborts.tryCatch()on the R side never gets a chance. The intended behavior is clearly a regular R error ("C++ object not initialized. (Missing default constructor?)").The
XPtrconstructor validation throws (not_compatible, null-pointer checks) in the same functions and inclass__newInstancecan escape the same way, though those require calling the internal entry points with malformed arguments; the dummy-object path is reachable from ordinary, documented usage.Reproducible example
repro.cpp:repro.R:Output with R 4.6.1 (macOS, Rcpp 1.1.2 --
src/module.cppis unchanged on current master):Rscriptexits with code 134 (SIGABRT); the finalcat()is never reached.I'll follow up with a PR that wraps these entry points in
BEGIN_RCPP/END_RCPP, so the exception is converted to the intended R error. (This also lets theXPtrlocals in those frames destruct properly on the error path instead of being skipped by the exception's escape.)