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.
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
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
int x = 3; int* y = x
, the compiler automatically
creates and manages memory for you.new
and delete
commands.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
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:
Initialize using a new pointer:
Initialize using existing pointer:
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