Rcpp简明指南: R调用C++

新版本参见 http://blog.sciencenet.cn/home.php?mod=space&uid=255662&do=blog&id=835970

注: 本文需要读者了解:

  1. R函数的基本知识
  2. 编写R程序包
  3. C++的基础知识, 包括 类, 以及namespace等。

为什么要用R调用C++函数? 原因可能有以下几点:

  1. R开发虽然很快, 涉及到一些循环的时候, 执行效率却常常相当于C++的1/100,甚至更慢

  2. C++中有一些已经开发好的库, 人们不希望再重写一遍R的代码。 而是最好通过编译之后直接调用。

Rcpp程序包为用R调用C++代码提供了方便。

用户直接在R的源代码src文件夹下加入cpp和h文件, rtools即可对C++文件进行编译。 但是为了从R的console能够直接调用cpp写的函数, 还必须对cpp的函数进行修改。

修改需要遵循以下原则:

  1. 所有从R输入到cpp的对象, 都是SEXP
  2. 从cpp返回到R的对象也必须是SEXP
  3. C++中, 返回值如为数值, 可以用wrap()帮忙, 直接返回为SEXP的对象, 实现R的调用

换句话说, 只有加了SEXP这样一个标签,C++函数在 R和C++中才都能识别。 Rcpp提供了SEXP处理的响应的类, 使得两者之间的数据传输变得容易。

以下是用R调用C++的一个简单例子:

如果在windows中要使用Rcpp, 则需要

  1. 安装Rcpp程序包,install.packages(“Rcpp”)

  2. 安装Rtools, 并配制好启动路径。电脑>属性>高级系统设置>高级>环境变量>系统变量>路径

  3. 在R console 中运行 library(Rcpp) , 之后调用 Rcpp.package.skeleton函数,

1
Rcpp.package.skeleton( "test" )

在生成的R包 skeleton中, 可以找到rcpp_hello_world.cpp文件, 可以尝试添加几个函数, 修改成如下样式 (注意以下代码为C++文件,扩展名为cpp):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
////////////////////// C++文件开始//////////////////
#include "rcpp_hello_world.h"
#include <Rcpp.h>
using namespace Rcpp;
using namespace std;
// 注意 #include <Rcpp.h> 是调用Rcpp的必要条件

RcppExport SEXP intVec1a(SEXP vec) {
Rcpp::NumericVector vec2(vec);
int prod = 1;
for (int i=0; i<vec2.size(); i++) {
prod *= vec2[i];
}
return (wrap(prod));
}

// RcppExport 是为了在载入R包后, 在Namespace内内能够找到这个C++的函数

//第一个SEXP声明返回值的类型

//第二个SEXP, 声明vec变量为SEXP的类型,也就是说, 它可以是从R直接传入

// Rcpp::NumericVector vec2(vec); 意思 是将 vec转换为 vec2, 从SEXP转换为 NumericVector, NumericVector是Rcpp下的一个类, 在C++的函数中可以识别和调用。 后面的代码就和普通的C++没有什么分别了。

//return (wrap(prod)); 由于我们希望在R中直接可以得到该函数的运行结果, 因此, 返回值也需要转换为SEXP类型。 wrap函数提供了一种便捷的方式。 简单的类型, 如int, double等, 只需要直接调用wrap即可。


RcppExport SEXP Eccentricity(SEXP JD)
{
double JD3 = Rcpp::as<double>(JD);
double T = (JD3 - 2451545.0) / 36525.0;
double Tsquared = T*T;
return (wrap(1 - 0.002516*T - 0.0000074*Tsquared));
}
// Rcpp::as<double>(JD); 表示调用Rcpp内的方法, 将类型为SEXP的JD, 转换为double类型, 并赋值給double JD3

RcppExport SEXP expfun(SEXP x){
double x2 = Rcpp::as<double>(x);
double ans=1.0, term=1.0, eps=1e-16;
int n=0;
while (fabs(term)>eps){
n++;
term = term * x2 / n;
ans = ans + term;
}
return(wrap(ans));
}
// C++文件结束//

对R程序包test的骨架进行修改后, 即可开始安装,

在windows下, 在 开始, cmd, 输入 Rcmd INSTALL test, 安装test程序包

之后转入 R console, library(test)

即可加载响应的动态链接库dll文件。 只是此时调用dll中的文件, 还稍微有些不方便。 此时调用dll中函数的方法如下

1
2
3
.Call('Eccentricity', JD= 2314543)
.Call('intVec1a', vec= c(3, 4, 5))
.Call('expfun', x=3)

不过每次使用.Call函数, 仍然让人觉得不太满意。为了需要写一个R的wrapper function例如

1
2
3
4
5
6
7
8
9
10
11
expfunR <- function(x){
return( .Call('expfun', x= x))
}

EccentricityR <- function(JD){
return( .Call('Eccentricity', JD= JD))
}

intVecR <- function(x){
return(.Call('intVec1a', vec= x))
}

调用方式同R函数,

1
2
3
expfunR(2)
EccentricityR(2314543)
intVecR(c(2,3,4))

这时候就和一般的R函数没有区别了。