Brian J. Knaus

Brian J. Knaus’s blog about genomics and biology.

Header files in Rcpp

In C++ you can create functions in order to help organize your code. This is helpful as your project grows or if you have a task that is performed by a function that several other functions may call. However, the functions you create are only visible to the other funcitons that are contained in a single source file. As your project grows, you may want to distribute your functions among several files. And if you do create that funcitons that is called by several other functions you may want to put it in its own file. But how do you get functions in different files to communicate with one another? The answer is to create header files. This post demonstrates how to create and use header files within the Rcpp framework.

A minimal R package

The use of header files is best illustrated by adding them to an R package. In order to illustrarte their use, we’ll need to create a minimal R package to work with. I suggest using the R package pkgKitten which can be installed from CRAN if you do not already have it.

R> library(pkgKitten)
R> kitten(name = "myRpackage")

Once we’ve created our package we can validate that it passes CRAN tests as follows.

bash$ R CMD build myRpackage
bash$ R CMD check --as-cran myRpackage_1.0.tar.gz

I generate one NOTE.

* checking CRAN incoming feasibility ... NOTE
Maintainer: ‘Your Name <your@email.com>’

New submission

For the purposes of this example, we can ignore this NOTE.

Header and source files

We’re now ready to add our header and source files. First, we’ll use devtools to easily add Rcpp to the package.

bash$ cd myRpackage
bash$ R
R> library(devtools)
R> use_rcpp()

You’ll want to follow the directions at the prompt. My solution was to add lines to the hello.R file. I’ve also modified this file from the one created by pkgKitten to include Roxygen comments.

File: R/hello.R

#' @title hello
#' @description
#' Says hello.
#' @name hello
#' @param txt something to say hello to
#' 
#' @export
hello <- function(txt = "world") {
    cat("Hello, ", txt, "\n")
}

#' @useDynLib myRpackage
#' @importFrom Rcpp sourceCpp
NULL

We can now add a header file as well as some Rcpp code files to our package. We’ll add a total of three files. The first file will be named modString.h. This is our header file and it declares our function. The second file will be named modString.cpp. This is the function that is declared in the header file. Lastly, we’ll create myFunction.cpp. This is Rcpp code that will call the function that is declared in the header file. These files are presented below.

File: modString.h

#ifndef MODSTRING_H
#define MODSTRING_H

#include <Rcpp.h>
Rcpp::StringVector modString(Rcpp::StringVector myStringV);

#endif

Note the inclusion of the following code.

#ifndef MODSTRING_H
#define MODSTRING_H
...
#endif

This is the ‘header guard’ and protects you from making multiple definitions of this code. This should always be a part of your header files.

File: modString.cpp

#include <string>
#include <sstream>
#include <Rcpp.h>

Rcpp::StringVector modString(Rcpp::StringVector myStringV)
{
  if(myStringV.size() > 1){
    myStringV[1] = "Rcpp";
  }
  return myStringV;
}

File: myFunction.cpp

#include <Rcpp.h>
#include "modString.h"

//' @title myFunction
//' @description
//' Modify a string in Rcpp.
//' @name myFunction
//' @param x a vector of strings
//' @examples
//' myFunction(x=c('Hello', "C++", 'header', 'files'))
//' 
//' @export
// [[Rcpp::export]]
Rcpp::StringVector myFunction(Rcpp::StringVector x) {
  x = modString(x);
  return x;
}

NAMESPACE file

The pkgKitten creates a NAMESPACE file for us. However, I like to use the Roxygen functionality to manage my NAMESPACE files. Because the pkgKitten NAMESPACE file was not created using Roxygen it will complain when we use devtools::document(). This is a problem because we do need to export the function we created to our namespace. I suggest creating your own NAMESPACE file as illustrated below. Once you’ve created this file it will be updated by Roxygen when documenting your package.

FILE: NAMESPACE

# Generated by roxygen2: do not edit by hand

export(hello)
export(myFunction)
importFrom(Rcpp,sourceCpp)
useDynLib(myRpackage)

Because we’ve added Roxygen comments to the hello.R file, we’ll want to remove its *.Rd so that Roxygen can generate one. Then we’ll want to document the project using devtools.

bash$ rm man/hello.Rd
bash$ R
R> devtools::document()
R> q()
bash$ cd ..

We should now be able to build and check our code.

bash$ R CMD build myRpackage
bash$ R CMD check --as-cran myRpackage_1.0.tar.gz

I generate the same NOTE mentioned above, and we can ignore it again.

Usage

We should now be able to install our package and test it.

R> install.packages("myRpackage_1.0.tar.gz", repos = NULL, type="source")
R> library('myRpackage')
R> myFunction(x=c('Hello', "C++", 'header', 'files'))

The function modString replaces the second element of the vector, “C++”, with “Rcpp”. This demonstrates functionality of our header file. We can now use functions from different files together.


Share

comments powered by Disqus