Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Disabling the default class constructor in Rcpp modules

Tags:

rcpp

I would like to disable the default (zero argument) constructor for a C++ class exposed to R using RCPP_MODULE so that calls to new(class) without any further arguments in R give an error. Here are the options I've tried:

1) Specifying a default constructor and throwing an error in the function body: this does what I need it to, but means specifying a default constructor that sets dummy values for all const member variables (which is tedious for my real use case)

2) Specifying that the default constructor is private (without a definition): this means the code won't compile if .constructor() is used in the Rcpp module, but has no effect if .constructor() is not used in the Rcpp module

3) Explicitly using delete on the default constructor: requires C++11 and seems to have the same (lack of) effect as (2)

I'm sure that I am missing something obvious, but can't for the life of me work out what it is. Does anyone have any ideas?

Thanks in advance,

Matt


Minimal example code (run in R):

inc <- '
using namespace Rcpp;

class Foo
{
public:
    Foo()
    {
        stop("Disallowed default constructor");
    }

    Foo(int arg)
    {
        Rprintf("Foo OK\\n");
    }
};

class Bar
{
private:
    Bar();
    // Also has no effect:
    // Bar() = delete;

public:
    Bar(int arg)
    {
        Rprintf("Bar OK\\n");
    }
};

RCPP_MODULE(mod) {

    class_<Foo>("Foo")
        .constructor("Disallowed default constructor")
        .constructor<int>("Intended 1-argument constructor")
    ;

    class_<Bar>("Bar")
        // Wont compile unless this line is commented out:
        // .constructor("Private default constructor")
        .constructor<int>("Intended 1-argument constructor")
    ;
} 
'

library('Rcpp')
library('inline')

fx <- cxxfunction(signature(), plugin="Rcpp", include=inc)
mod <- Module("mod", getDynLib(fx))

# OK as expected:
new(mod$Foo, 1)
# Fails as expected:
new(mod$Foo)

# OK as expected:
new(mod$Bar, 1)
# Unexpectedly succeeds:
new(mod$Bar)

How can I get new(mod$Bar) to fail without resorting to the solution used for Foo?


EDIT


I have discovered that my question is actually a symptom of something else:

#include <Rcpp.h>

class Foo {
public:
    int m_Var;
    Foo() {Rcpp::stop("Disallowed default constructor"); m_Var=0;}
    Foo(int arg) {Rprintf("1-argument constructor\n"); m_Var=1;}
    int GetVar() {return m_Var;}
};

RCPP_MODULE(mod) {
    Rcpp::class_<Foo>("Foo")
        .constructor<int>("Intended 1-argument constructor")
        .property("m_Var", &Foo::GetVar, "Get value assigned to m_Var")
    ;
} 

/*** R
# OK as expected:
f1 <- new(Foo, 1)
# Value set in the 1-parameter constructor as expected:
f1$m_Var

# Unexpectedly succeeds without the error message:
tryCatch(f0 <- new(Foo), error = print)
# This is the type of error I was expecting to see:
tryCatch(f2 <- new(Foo, 1, 2), error = print)

# Note that f0 is not viable (and sometimes brings down my R session):
tryCatch(f0$m_Var, error = print)
*/

[With acknowledgements to @RalfStubner for the improved code]

So in fact it seems that new(Foo) is not actually calling any C++ constructor at all for Foo, so my question was somewhat off-base ... sorry.

I guess there is no way to prevent this happening at the C++ level, so maybe it makes most sense to use a wrapper function around the call to new(Foo) at the R level, or continue to specify a default constructor that throws an error. Both of these solutions will work fine - I was just curious as to exactly why my expectation regarding the absent default constructor was wrong :)

As a follow-up question: does anybody know exactly what is happening in f0 <- new(Foo) above? My limited understanding suggests that although f0 is created in R, the associated pointer leads to something that has not been (correctly/fully) allocated in C++?

like image 886
Matt Denwood Avatar asked Apr 08 '26 01:04

Matt Denwood


1 Answers

After a bit of experimentation I have found a simple solution to my problem that is obvious in retrospect...! All I needed to do was use .factory for the default constructor along with a function that takes no arguments and just throws an error. The default constructor for the Class is never actually referenced so doesn't need to be defined, but it obtains the desired behaviour in R (i.e. an error if the user mistakenly calls new with no additional arguments).

Here is an example showing the solution (Foo_A) and a clearer illustration of the problem (Foo_B):

#include <Rcpp.h>

class Foo {
private:
    Foo();   // Or for C++11:  Foo() = delete;
    const int m_Var;
public:
    Foo(int arg) : m_Var(arg) {Rcpp::Rcout << "Constructor with value " << m_Var << "\n";}
    void ptrAdd() const {Rcpp::Rcout << "Pointer: " << (void*) this << "\n";}
};

Foo* dummy_factory() {Rcpp::stop("Default constructor is disabled for this class"); return 0;}

RCPP_MODULE(mod) {
    Rcpp::class_<Foo>("Foo_A")
        .factory(dummy_factory)  // Disable the default constructor
        .constructor<int>("Intended 1-argument constructor")
        .method("ptrAdd", &Foo::ptrAdd, "Show the pointer address")
    ;

    Rcpp::class_<Foo>("Foo_B")
        .constructor<int>("Intended 1-argument constructor")
        .method("ptrAdd", &Foo::ptrAdd, "Show the pointer address")
    ;
} 

/*** R
# OK as expected:
fa1 <- new(Foo_A, 1)
# Error as expected:
tryCatch(fa0 <- new(Foo_A), error = print)

# OK as expected:
fb1 <- new(Foo_B, 1)
# No error:
tryCatch(fb0 <- new(Foo_B), error = print)
# But this terminates R with the following (quite helpful!) message:
# terminating with uncaught exception of type Rcpp::not_initialized: C++ object not initialized. (Missing default constructor?)
tryCatch(fb0$ptrAdd(), error = print)
*/

As was suggested to me in a comment I have started a discussion at https://github.com/RcppCore/Rcpp/issues/970 relating to this.

like image 90
Matt Denwood Avatar answered Apr 23 '26 00:04

Matt Denwood



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!