Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
2026-10-22 Michael Sumner <mdsumner@gmail.com>

* R/00_classes.R: Define C++Field, C++OverloadedMethods and C++Constructor
as plain S4 classes instead of reference classes, as creating one reference
class object per exposed method dominated the time spent loading a module;
add '$' and '$<-' methods for backwards compatibility
* R/00_classes.R (.cpp_methods_info): Former 'info' reference method
* R/01_show.R: Use slot access and .cpp_methods_info()
* R/Module.R (method_wrapper, binding_maker): Idem
* inst/include/Rcpp/Module.h: Derive from S4 instead of Reference
* man/CppConstructor-class.Rd: Document slots and '$' methods
* man/CppField-class.Rd: Idem
* man/CppOverloadedMethods-class.Rd: Idem

2026-10-21 Michael Sumner <mdsumner@gmail.com>

* R/Module.R (Module): Supply the 'initialize' method to setRefClass() with
Expand Down
81 changes: 53 additions & 28 deletions R/00_classes.R
Original file line number Diff line number Diff line change
Expand Up @@ -24,47 +24,72 @@ setClassUnion("refGenerator", c("refObjectGenerator", "refClassGeneratorFunction
## Stands in for a reference class with those fields.
setClass( "Module", contains = "environment" )

setRefClass( "C++Field",
fields = list(
pointer = "externalptr",
cpp_class = "character",
read_only = "logical",
class_pointer = "externalptr",
## Plain S4 classes describing the fields, methods and constructors that a
## C++ class exposes. These used to be reference classes; creating one
## reference-class object per exposed method (via new()) dominated the
## time spent loading a module. A "$" method is provided below so that
## existing code using field-style access (x$pointer, x$info()) keeps working.
setClass( "C++Field",
representation(
pointer = "externalptr",
cpp_class = "character",
read_only = "logical",
class_pointer = "externalptr",
docstring = "character"
)
)

setRefClass( "C++OverloadedMethods",
fields = list(
pointer = "externalptr",
class_pointer = "externalptr",
size = "integer",
setClass( "C++OverloadedMethods",
representation(
pointer = "externalptr",
class_pointer = "externalptr",
size = "integer",
void = "logical",
const = "logical",
docstrings = "character",
signatures = "character",
const = "logical",
docstrings = "character",
signatures = "character",
nargs = "integer"
),
methods = list(
info = function(prefix = " " ){
paste(
paste( prefix, signatures, ifelse(const, " const", "" ), "\n", prefix, prefix,
ifelse( nchar(docstrings), paste( "docstring :", docstrings) , "" )
) , collapse = "\n" )
}
)
)

setRefClass( "C++Constructor",
fields = list(
pointer = "externalptr",
class_pointer = "externalptr",
nargs = "integer",
signature = "character",
setClass( "C++Constructor",
representation(
pointer = "externalptr",
class_pointer = "externalptr",
nargs = "integer",
signature = "character",
docstring = "character"
)
)

## formerly the 'info' reference method of C++OverloadedMethods
.cpp_methods_info <- function( x, prefix = " " ){
paste(
paste( prefix, x@signatures, ifelse(x@const, " const", "" ), "\n", prefix, prefix,
ifelse( nchar(x@docstrings), paste( "docstring :", x@docstrings) , "" )
) , collapse = "\n" )
}
Comment on lines +66 to +71

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need this? This is called once per method, so after the savings of this PR, this probably takes a lot of time. S4_CppOverloadedMethods in the C++ side already loops over every overload, so we could build each signature and store it in a docinfo slot.


## backwards compatible field-style access
setMethod( "$", "C++OverloadedMethods", function(x, name){
if( identical( name, "info" ) )
function( prefix = " " ) .cpp_methods_info( x, prefix )
else
methods::slot( x, name )
} )
setMethod( "$", "C++Field", function(x, name) methods::slot( x, name ) )
setMethod( "$", "C++Constructor", function(x, name) methods::slot( x, name ) )

## packages compiled against earlier versions of the Rcpp headers populate
## these objects with `$<-` (FieldProxy); keep those binaries loadable
.slot_dollar_assign <- function(x, name, value) {
methods::slot( x, name ) <- value
x
}
setReplaceMethod( "$", "C++OverloadedMethods", .slot_dollar_assign )
setReplaceMethod( "$", "C++Field", .slot_dollar_assign )
setReplaceMethod( "$", "C++Constructor", .slot_dollar_assign )

setClass( "C++Class",
representation(
pointer = "externalptr",
Expand Down
12 changes: 6 additions & 6 deletions R/01_show.R
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ setMethod( "show", "C++Class", function(object){
txt <- character( nctors )
for( i in seq_len(nctors) ){
ctor <- ctors[[i]]
doc <- ctor$docstring
txt[i] <- sprintf( " %s%s", ctor$signature, if( nchar(doc) ) sprintf( "\n docstring : %s", doc) else "" )
doc <- ctor@docstring
txt[i] <- sprintf( " %s%s", ctor@signature, if( nchar(doc) ) sprintf( "\n docstring : %s", doc) else "" )
}
writeLines( "Constructors:" )
writeLines( paste( txt, collapse = "\n" ) )
Expand All @@ -55,11 +55,11 @@ setMethod( "show", "C++Class", function(object){
writeLines( "\nFields: " )
for( i in seq_len(nfields) ){
f <- fields[[i]]
doc <- f$docstring
doc <- f@docstring
txt[i] <- sprintf( " %s %s%s%s",
f$cpp_class,
f@cpp_class,
names[i],
if( f$read_only ) " [readonly]" else "",
if( f@read_only ) " [readonly]" else "",
if( nchar(doc) ) sprintf( "\n docstring : %s", doc ) else ""
)
}
Expand All @@ -74,7 +74,7 @@ setMethod( "show", "C++Class", function(object){
writeLines( "\nMethods: " )
txt <- character( nmethods )
for( i in seq_len(nmethods) ){
txt[i] <- mets[[i]]$info(" ")
txt[i] <- .cpp_methods_info( mets[[i]], " " )
}
writeLines( paste( txt, collapse = "\n" ) )
} else {
Expand Down
20 changes: 10 additions & 10 deletions R/Module.R
Original file line number Diff line number Diff line change
Expand Up @@ -326,31 +326,31 @@ Module <- function( module, PACKAGE = methods::getPackageName(where), where = to
dealWith <- function( x ) if(isTRUE(x[[1]])) invisible(NULL) else x[[2]] # #nocov

method_wrapper <- function( METHOD, where ){
noargs <- all( METHOD$nargs == 0 )
noargs <- all( METHOD@nargs == 0 )
stuff <- list(
class_pointer = METHOD$class_pointer,
pointer = METHOD$pointer,
class_pointer = METHOD@class_pointer,
pointer = METHOD@pointer,
CppMethod__invoke = CppMethod__invoke,
CppMethod__invoke_void = CppMethod__invoke_void,
CppMethod__invoke_notvoid = CppMethod__invoke_notvoid,
dealWith = dealWith,
docstring = METHOD$info("")
docstring = .cpp_methods_info( METHOD, "" )
)
f <- function(...) NULL
if( noargs ){
formals(f) <- NULL
}

extCall <- if( noargs ) {
if( all( METHOD$void ) ){
if( all( METHOD@void ) ){
# all methods are void, so we know we want to return invisible(NULL)
substitute(
{
docstring
.External(CppMethod__invoke_void, class_pointer, pointer, .pointer )
invisible(NULL)
} , stuff )
} else if( all( ! METHOD$void ) ){
} else if( all( ! METHOD@void ) ){
# none of the methods are void so we always return the result of
# .External
substitute(
Expand All @@ -368,15 +368,15 @@ method_wrapper <- function( METHOD, where ){
} , stuff ) # #nocov end
}
} else {
if( all( METHOD$void ) ){
if( all( METHOD@void ) ){
# all methods are void, so we know we want to return invisible(NULL)
substitute(
{
docstring
.External(CppMethod__invoke_void, class_pointer, pointer, .pointer, ...)
invisible(NULL)
} , stuff )
} else if( all( ! METHOD$void ) ){
} else if( all( ! METHOD@void ) ){
# none of the methods are void so we always return the result of
# .External
substitute(
Expand Down Expand Up @@ -428,8 +428,8 @@ binding_maker <- function( FIELD, where ){
.Call( CppField__get, class_pointer, pointer, .pointer)
else
.Call( CppField__set, class_pointer, pointer, .pointer, x)
}, list(class_pointer = FIELD$class_pointer,
pointer = FIELD$pointer,
}, list(class_pointer = FIELD@class_pointer,
pointer = FIELD@pointer,
CppField__get = CppField__get,
CppField__set = CppField__set ))
environment(f) <- where
Expand Down
56 changes: 28 additions & 28 deletions inst/include/Rcpp/Module.h
Original file line number Diff line number Diff line change
Expand Up @@ -310,35 +310,35 @@ namespace Rcpp{
} ;

template <typename Class>
class S4_CppConstructor : public Reference {
typedef Reference Base;
class S4_CppConstructor : public S4 {
typedef S4 Base;
public:
typedef XPtr<class_Base> XP_Class ;
typedef Reference::Storage Storage ;
typedef S4::Storage Storage ;

S4_CppConstructor( SignedConstructor<Class>* m, const XP_Class& class_xp, const std::string& class_name, std::string& buffer ) : Reference( "C++Constructor" ){
S4_CppConstructor( SignedConstructor<Class>* m, const XP_Class& class_xp, const std::string& class_name, std::string& buffer ) : S4( "C++Constructor" ){
RCPP_DEBUG( "S4_CppConstructor( SignedConstructor<Class>* m, SEXP class_xp, const std::string& class_name, std::string& buffer" ) ;
field( "pointer" ) = Rcpp::XPtr< SignedConstructor<Class> >( m, false ) ;
field( "class_pointer" ) = class_xp ;
field( "nargs" ) = m->nargs() ;
slot( "pointer" ) = Rcpp::XPtr< SignedConstructor<Class> >( m, false ) ;
slot( "class_pointer" ) = class_xp ;
slot( "nargs" ) = m->nargs() ;
m->signature( buffer, class_name ) ;
field( "signature" ) = buffer ;
field( "docstring" ) = m->docstring ;
slot( "signature" ) = buffer ;
slot( "docstring" ) = m->docstring ;
}

RCPP_CTOR_ASSIGN_WITH_BASE(S4_CppConstructor)

} ;

template <typename Class>
class S4_CppOverloadedMethods : public Rcpp::Reference {
typedef Rcpp::Reference Base;
class S4_CppOverloadedMethods : public Rcpp::S4 {
typedef Rcpp::S4 Base;
public:
typedef Rcpp::XPtr<class_Base> XP_Class ;
typedef SignedMethod<Class> signed_method_class ;
typedef std::vector<signed_method_class*> vec_signed_method ;

S4_CppOverloadedMethods( vec_signed_method* m, const XP_Class& class_xp, const char* name, std::string& buffer ) : Reference( "C++OverloadedMethods" ){
S4_CppOverloadedMethods( vec_signed_method* m, const XP_Class& class_xp, const char* name, std::string& buffer ) : S4( "C++OverloadedMethods" ){
int n = static_cast<int>(m->size()) ;
Rcpp::LogicalVector voidness(n), constness(n) ;
Rcpp::CharacterVector docstrings(n), signatures(n) ;
Expand All @@ -354,14 +354,14 @@ namespace Rcpp{
signatures[i] = buffer ;
}

field( "pointer" ) = Rcpp::XPtr< vec_signed_method >( m, false ) ;
field( "class_pointer" ) = class_xp ;
field( "size" ) = n ;
field( "void" ) = voidness ;
field( "const" ) = constness ;
field( "docstrings" ) = docstrings ;
field( "signatures" ) = signatures ;
field( "nargs" ) = nargs ;
slot( "pointer" ) = Rcpp::XPtr< vec_signed_method >( m, false ) ;
slot( "class_pointer" ) = class_xp ;
slot( "size" ) = n ;
slot( "void" ) = voidness ;
slot( "const" ) = constness ;
slot( "docstrings" ) = docstrings ;
slot( "signatures" ) = signatures ;
slot( "nargs" ) = nargs ;

}

Expand Down Expand Up @@ -488,17 +488,17 @@ namespace Rcpp{
} ;

template <typename Class>
class S4_field : public Rcpp::Reference {
typedef Rcpp::Reference Base;
class S4_field : public Rcpp::S4 {
typedef Rcpp::S4 Base;
public:
typedef XPtr<class_Base> XP_Class ;
S4_field( CppProperty<Class>* p, const XP_Class& class_xp ) : Reference( "C++Field" ){
S4_field( CppProperty<Class>* p, const XP_Class& class_xp ) : S4( "C++Field" ){
RCPP_DEBUG( "S4_field( CppProperty<Class>* p, const XP_Class& class_xp )" )
field( "read_only" ) = p->is_readonly() ;
field( "cpp_class" ) = p->get_class();
field( "pointer" ) = Rcpp::XPtr< CppProperty<Class> >( p, false ) ;
field( "class_pointer" ) = class_xp ;
field( "docstring" ) = p->docstring ;
slot( "read_only" ) = p->is_readonly() ;
slot( "cpp_class" ) = p->get_class();
slot( "pointer" ) = Rcpp::XPtr< CppProperty<Class> >( p, false ) ;
slot( "class_pointer" ) = class_xp ;
slot( "docstring" ) = p->docstring ;
}

RCPP_CTOR_ASSIGN_WITH_BASE(S4_field)
Expand Down
20 changes: 13 additions & 7 deletions man/CppConstructor-class.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@
\Rdversion{1.1}
\docType{class}
\alias{C++Constructor-class}
\alias{$,C++Constructor-method}
\alias{$<-,C++Constructor-method}

\title{Class "C++Constructor"}
\description{
Representation of a C++ constructor
}
\section{Extends}{
Class \code{"\linkS4class{envRefClass}"}, directly.
Class \code{"\linkS4class{.environment}"}, by class "envRefClass", distance 2.
Class \code{"\linkS4class{refClass}"}, by class "envRefClass", distance 2.
Class \code{"\linkS4class{environment}"}, by class "envRefClass", distance 3, with explicit coerce.
Class \code{"\linkS4class{refObject}"}, by class "envRefClass", distance 3.
\section{Methods}{
\describe{
\item{$}{\code{signature(x = "C++Constructor")}: slot access, kept for backwards compatibility }
\item{$<-}{\code{signature(x = "C++Constructor")}: slot assignment, kept for backwards compatibility }
}
}
\note{
This used to be a reference class. Objects of this class are plain S4
objects now; the slots may still be accessed with \code{$} for backwards
compatibility.
}
\keyword{classes}
\section{Fields}{
\section{Slots}{
\describe{
\item{\code{pointer}:}{pointer to the internal structure that represent the constructor}
\item{\code{class_pointer}:}{pointer to the internal structure that represent the associated C++ class}
Expand Down
15 changes: 13 additions & 2 deletions man/CppField-class.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,27 @@
\Rdversion{1.1}
\docType{class}
\alias{C++Field-class}
\alias{$,C++Field-method}
\alias{$<-,C++Field-method}

\title{Class "C++Field"}
\description{
Metadata associated with a field of a class exposed through Rcpp modules
}
\section{Fields}{
\section{Slots}{
\describe{
\item{\code{pointer}:}{external pointer to the internal (C++) object that represents fields}
\item{\code{cpp_class}:}{(demangled) name of the C++ class of the field}
\item{\code{read_only}:}{Is this field read only}
\item{\code{class_pointer}:}{external pointer to the class this field is from. }
\item{\code{docstring}:}{docstring of the field}
}
}
\section{Methods}{
No methods defined with class "C++Field" in the signature.
\describe{
\item{$}{\code{signature(x = "C++Field")}: slot access, kept for backwards compatibility }
\item{$<-}{\code{signature(x = "C++Field")}: slot assignment, kept for backwards compatibility }
}
}
\seealso{
The \code{fields} slot of the \code{\linkS4class{C++Class}} class is a
Expand All @@ -25,4 +31,9 @@ No methods defined with class "C++Field" in the signature.
\examples{
showClass("C++Field")
}
\note{
This used to be a reference class. Objects of this class are plain S4
objects now; the slots may still be accessed with \code{$} for backwards
compatibility.
}
\keyword{classes}
Loading
Loading