coatless-rd-rcpp / rcpp-modules-bank-account

Rcpp: Export a C++ Class into R using Rcpp Modules. Example given by constructing a bank account class and exporting it into R

Home Page:http://rd-rcpp.thecoatlessprofessor.com/rcpp-modules-bank-account/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Exposing C++ Classes into R Through Rcpp Modules

R-CMD-check

The RcppBankAccount R package provides an example of using Rcpp Modules to expose C++ classes and their methods to R. The example hails from working with a BankAccount class that is a staple of tutorials on C++ classes.

Usage

To install the package, you must first have a compiler on your system that is compatible with R. For help on obtaining a compiler consult either macOS or Windows guides.

With a compiler in hand, one can then install the package from GitHub by:

# install.packages("remotes")
remotes::install_github("coatless-rd-rcpp/rcpp-modules-bank-account")
library("RcppBankAccount")

Implementation Details

This guide focuses on providing an implementation along the path suggested in Rcpp Modules Section 2.2: Exposing C++ classes using Rcpp modules. In particular, the focus is to expose a pure C++ class inside of R without modifying the underlying C++ class. Largely, this means that the C++ class must be "marked up" for export using RCPP_MODULE( ... ) macro in a separate file.

RcppBankAccount
├── DESCRIPTION                  # Package metadata
├── NAMESPACE                    # Function and dependency registration
├── R
│   ├── RcppExports.R
│   ├── BankAccount-exports.R        # Exporting Rcpp Module's BankAccount into R
│   └── BankAccount-pkg.R            # NAMESPACE Import Code for Rcpp Modules
├── README.md                        # Implementation Overview
├── RcppBankAccount.Rproj
├── man
│   ├── RcppBankAccount-package.Rd
│   └── BankAccount.Rd
└── src
    ├── Makevars                     # Enable C++11
    ├── RcppExports.cpp
    ├── BankAccount.cpp              # Class Implementation
    ├── BankAccount.h                # Class Definition
    └── BankAccount_export.cpp       # Exporting the C++ Class with RCPP_MODULE

C++ Class Definition

Inside of src/BankAccount.h, the definition of the C++ class is written with an inclusion guard. The definition is a "bare-bones" overview of what to expect. The meat or the implementation of the class is given in the src/BankAccount.cpp file.

//  BankAccount.h

#ifndef BankAccount_H
#define BankAccount_H

class BankAccount
{

public:

  // Constructors
  BankAccount();
  BankAccount(int starting_balance);

  // Modifiers
  void deposit(int amount);
  void withdraw(int amount);

  // Accessor
  int get_current_balance();

private:

  // Member variables
  int current_balance;
};

#endif /* BankAccount_H */

C++ Class Implementation

In src/BankAccount.cpp, the meat behind the C++ class is implemented. The "meat" emphasizes how different methods within the class should behave. By

//  BankAccount.cpp

// Required to use stop()
#include <Rcpp.h>

// Retrieve the definition of our BankAccount class
#include "BankAccount.h"

// Constructors ----
BankAccount::BankAccount() {
  current_balance = 250;
}

BankAccount::BankAccount(int starting_balance) {
  current_balance = starting_balance;
}

// Modifiers ----
void BankAccount::deposit(int amount) {
  current_balance += amount;
}

void BankAccount::withdraw(int amount) {
  if (current_balance >= amount) {
    current_balance -= amount;
  } else {
    Rcpp::stop("Current balance is %s so we cannot withdraw %s without going negative.", current_balance, amount);
  }
}

// Accessors ----
int BankAccount::get_current_balance() {
  return current_balance;
}

Writing the Glue

With the class definition and implementation in hand, the task switches to exposing the definition into R by creating src/BankAccount_export.cpp. Within this file, the Rcpp Module is defined using the RCPP_MODULE( ... ) macro. Through the macro, the class' information must be specified using different member functions of Rcpp::class_. A subset of these member functions is given next:

  • Constructor:
    • .default_constructor()
      • Exposes the default class constructor that does not have any parameters.
    • .constructor<PARAMTYPE1, PARAMTYPE2>()
      • Exposes a constructor with recognizable C++ data types.
      • e.g. double, int, std::string, and so on.
  • Methods:
    • .method("FunctionName", &ClassName::FunctionName)
      • Exposes a class method from ClassName given by FunctionName.
    • .property("VariableName", &ClassName::GetFunction, &ClassName::SetFunction )
      • Indirect access to a Class' fields through getter and setter functions.
  • Fields:
    • .field("VariableName", &ClassName::VariableName, "documentation for VariableName")
      • Exposes a public field with read and write access from R
    • .field_readonly("VariableName", &Foo::VariableName, "documentation for VariableName read only")
      • Exposes a public field with read access from R
// BankAccount_export.cpp

// Include Rcpp system header file (e.g. <>)
#include <Rcpp.h>

// Include our definition of the BankAccount file (e.g. "")
#include "BankAccount.h"

// Expose (some of) the Student class
RCPP_MODULE(RcppBankAccountEx){
  Rcpp::class_<BankAccount>("BankAccount")
  .default_constructor()
  .constructor<int>()
  .method("deposit", &BankAccount::deposit)
  .method("withdraw", &BankAccount::withdraw)
  .property("get_current_balance", &BankAccount::get_current_balance);
}

Exporting and documenting an Rcpp Module

In the R/BankAccount-exports.R file, write the load statement for the module exposed via the RCPP_MODULE( ... ) macro. In addition, make sure to export the class name so that when the package is loaded anyone can access it via new().

# Export the "BankAccount" C++ class by explicitly requesting BankAccount be
# exported via roxygen2's export tag.
#' @export BankAccount

loadModule(module = "RcppBankAccountEx", TRUE)

Package Structure

To register the required components for Rcpp Modules, the NAMESPACE file must be populated with imports for Rcpp and the methods R packages. In addition, the package's dynamic library must be specified as well.

There are two ways to go about this:

  1. Let roxygen2 automatically generate the NAMESPACE file; or
  2. Manually specify in the NAMESPACE file.

The roxygen2 markup required can be found in R/RcppBankAccount-package.R.

#' @useDynLib RcppBankAccount, .registration = TRUE
#' @import methods Rcpp
"_PACKAGE"

Once the above is run during the documentation generation phase, the NAMESPACE file will be created with:

# Generated by roxygen2: do not edit by hand

export(BankAccount)
import(Rcpp)
import(methods)
useDynLib(RcppBankAccount, .registration = TRUE)

Make sure to build and reload the package prior to accessing methods.

Calling an Rcpp Module in R

At this point, everything boils down to constructing an object from the class using new() from the methods package. The new() function initializes a C++ object from the specified C++ class and treats it like a traditional S4 object.

##################
## Constructor

# Use a default constructor
jjb_bank = new(BankAccount)

# Supply values to the constructor
pete_bank = new(BankAccount, starting_balance = 1000)

##################
## Setters

jjb_bank$deposit(10)

jjb_bank$withdraw(20)

# This will generate an error
jjb_bank$withdraw(10000)
# Error in jjb_bank$withdraw(10000) : 
#   Current balance is 240 so we cannot withdraw 10000 without going negative.


##################
## Getters

jjb_bank$get_current_balance
# [1] 240

License

GPL (>= 2)

About

Rcpp: Export a C++ Class into R using Rcpp Modules. Example given by constructing a bank account class and exporting it into R

http://rd-rcpp.thecoatlessprofessor.com/rcpp-modules-bank-account/


Languages

Language:C++ 65.9%Language:R 34.1%