Rcpp

This tutorial will use Rcpp to write C++ code directly in R so that R users can interact with C++ code in a familiar environment. C++ code is wrapped in the Rcpp function, cppFunction(), which when run, will automatically compile the C++ code and expose the C++ function to the R environment.

Definitions:

C++ Reference: - A reference variable is an alias for an existing variable. - It is created using the & operator, e.g. int y = 3; int &x = y - The operator & can also be used to return the memory address of a variable, e.g. &x

cppFunction('
#include <Rcpp.h> 
int reference() {
  int y = 3;
  int &x = y;
  Rcpp::Rcout << "x is: " << x << std::endl;
  Rcpp::Rcout << "y is: " << y << std::endl;
  Rcpp::Rcout << "The memory address of x is: " << &x << std::endl;
  Rcpp::Rcout << "The memory address of y is: " << &y << std::endl;
  
  return 0;
}')

reference()
## x is: 3
## y is: 3
## The memory address of x is: 0x7ffca4f5f9d4
## The memory address of y is: 0x7ffca4f5f9d4
## [1] 0

C++ Pointer: - A variable that stores the memory address as its value. - It is created using the * operator, e.g. int y = 3; int* x = &y; - The operator * can also be used to access the value the pointer points to, e.g. *x - In FIMS, we used shared pointer declared, std::shared_ptr<int> y which we will explore later.

cppFunction('
#include <Rcpp.h> 
int pointer(){
  float y = 3.1459;
  //initiate a variable x that points to the same address as y
  float* x = &y;
  Rcpp::Rcout << "x is equal to the address of y" << std::endl;
  Rcpp::Rcout << "x is: " << x << std::endl;
  Rcpp::Rcout <<  "The address of y is: " << &y << std::endl;
  Rcpp::Rcout << "*x returns the value of y: " << *x <<  std::endl;
  return 0;
}')

pointer()
## x is equal to the address of y
## x is: 0x7ffca4f5f9d4
## The address of y is: 0x7ffca4f5f9d4
## *x returns the value of y: 3.1459
## [1] 0

Modifying Pointers

We can use pointers to update values. In the following example, b is a copy while c is a pointer, which is why b does not get updated when *c is updated. We avoid copies in FIMS because when a variable gets updated in the model, we want it updated everywhere in the model.

cppFunction('
#include <Rcpp.h> 
int update_pointer(){
 //initiate a variable
  float a = 3.1459;
  //initiate a new variable with the same value as a
  float b = a;
  //initiate a variable c that points to the same address as a
  float* c = &a;
  *c = 100;
  Rcpp::Rcout << "a and *c have been updated; b has not"  << std::endl;
  Rcpp::Rcout << "a = " << a << "; *c = " << *c << std::endl;
  Rcpp::Rcout << "b = " << b << std::endl;
  return 0;
}')

update_pointer()
## a and *c have been updated; b has not
## a = 100; *c = 100
## b = 3.1459
## [1] 0

We can reassign pointers using the & operator. What do you expect a and *c` to return in the following example?

cppFunction('
#include <Rcpp.h> 
int reassign_pointer(){
//initiate a variable
  float a = 3.1459;
  //initiate a new variable with the same value as a
  float b = a;
  //initiate a variable c that points to the same address as a
  float* c = &a;
  *c = 100;
  c = &b;
  b = 10;
  Rcpp::Rcout << "c now equals the address of b" << std::endl;
  Rcpp::Rcout << "&a = " << &a << std::endl;
  Rcpp::Rcout << "&b = " << &b << std::endl;
  Rcpp::Rcout << "c = " << c << std::endl; 
  Rcpp::Rcout << "a = " << a << std::endl;
  Rcpp::Rcout << "b = " << b << std::endl; 
  Rcpp::Rcout << "*c = " << *c <<std::endl;
  return 0;
}')

reassign_pointer()
## c now equals the address of b
## &a = 0x7ffca4f5f9d0
## &b = 0x7ffca4f5f9d4
## c = 0x7ffca4f5f9d4
## a = 100
## b = 10
## *c = 10
## [1] 0

Memory Management

  • When pointers are initialized inline, e.g. int x = 3; int* y = x, the compiler automatically creates and manages memory for you.
  • The user can also create and manage memory themselves using the new and delete commands.
  • Care must be taken to manage memory properly.
  • If you create a variable using new and don’t clean up after using delete, the program can result in memory leaks where the memory in the program accumulates over its run time, which can slow down or even crash your program.
cppFunction('
#include <Rcpp.h> 
int manage_memory(){
  //initiate an integer type pointer
  int* ptr= new int;
  *ptr = 35;
  Rcpp::Rcout << "*ptr = " << *ptr <<std::endl;
  
  //delete memory 
  delete ptr;
  return 0;
}')

manage_memory()
## *ptr = 35
## [1] 0

Shared Pointers

FIMS uses shared pointers, also called smart pointers, to help with memory management. It is recommended to use a shared pointer when the ownership of an object is shared across the program. For example in FIMS, the recruitment module needs to be accessed by information.hpp and population.hpp. The pointer to the recruitment module, is therefore shared across the program. C++ automatically deallocates memory to a shared pointer when the object goes out of scope, thus preventing memory leaks in the program.

Specifically, each shared pointers has a reference count that tracks the number of instances in which the shared points points to the same object. In the example above, information and population both reference the recruitment module using a shared pointer, so the recruitment module pointer would have a reference count of two. When the reference count drops to zero, the memory where the recruitment module is saved would be automatically deleted.

Declare a shared pointer:

std::shared_ptr<Type> ptr;

Initialize using a new pointer:

std::shared_ptr<Type>* ptr;
std::shared_ptr<Type> ptr = std::makes_shared<Type>();

Initialize using existing pointer:

double* x = 3.2; 
shared_ptr<double> ptr(x);
shared_ptr<double> ptr = make_shared(x);

Shared Pointer Example:

cppFunction('
  #include <Rcpp.h> 
  //std::shared_ptr is defined in <memory>
  #include <memory>
  int shared_pointer(){
   // Creating shared pointers using std::make_shared
    std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
    std::shared_ptr<int> ptr2 = std::make_shared<int>(24);
    // Accessing the values using the (*) operator
    Rcpp::Rcout << "ptr1: " << *ptr1 << std::endl;
    Rcpp::Rcout << "ptr2: " << *ptr2 << std::endl;
    // Set up a new pointer that shares ownership with the ptr1
    std::shared_ptr<int> ptr3 = ptr1;
    // Checking if shared pointer 1 and shared pointer 3
    Rcpp::Rcout << "ptr1 = " << ptr1 << std::endl;
    Rcpp::Rcout << "ptr2 = " << ptr2 << std::endl;
    Rcpp::Rcout << "ptr3 = " << ptr3 << std::endl;
    
    return 0;
}')

shared_pointer()
## ptr1: 42
## ptr2: 24
## ptr1 = 0x5555cdfbdce0
## ptr2 = 0x5555cdbc1520
## ptr3 = 0x5555cdfbdce0
## [1] 0