39 Rcpp 属性
39.1 Rcpp属性介绍
Rcpp属性(attributes)用来简化把C++函数变成R函数的过程。 做法是在C++源程序中加入一些特殊注释, 利用其指示自动生成C++与R的接口程序。 属性是C++11标准的内容, 现在的编译器支持还不多, 所以在Rcpp支持的C++程序中写成了特殊格式的注释。
Rcpp属性有如下优点:
降低了同时使用R与C++的学习难度;
取消了很多繁复的接口代码;
可以在R会话中很简单地调用C++代码, 不需要用户自己考虑编译、连接、接口问题;
可以先交互地调用C++, 成熟后改编为R扩展包而不需要修改界面代码。
Rcpp属性的主要组成部分如下:
在C++中,提供
Rcpp::export
标注要输出到R中的C++函数。在R中,提供
sourceCpp()
, 用来自动编译连接保存在文件或R字符串中的C++代码, 并自动生成界面程序把C++函数转换为R函数。在R中,提供
cppFunction()
函数, 用来把保存在R字符串中的C++函数自动编译连接并转换成R函数。 提供evalCpp()
函数, 用来把保存在R字符串中的C++代码片段自动编译连接并执行。在C++中,提供
Rcpp::depends
标注, 说明编译连接时需要的外部头文件和库的位置。在构建R扩展包时,提供
compileAttributes()
R函数, 自动给C++函数生成相应的extern C
声明和.Call
接口代码。
39.2 在C++源程序中指定要导出的C++函数
用特殊注释//[[Rcpp::export]]
说明某C++函数需要在编译成动态链接库时,
把这个函数导出到链接库的对外可见部分。
例如
具体程序参见前面“用sourceCpp()
转换C++源程序文件—卷积例子”。
假设此C++源程序保存到了当前工作目录的conv.cpp源文件中,
为了在R中调用此C++程序,只要用如:
注意sourceCpp()
把C++源程序自动进行了编译链接并转换成了同名的R函数。
在同一R会话内,
如果源程序和其依赖资源没有变化(根据文件更新时间判断),
就不重新编译C++源代码。
在用特殊注释说明要导出的C++函数时, 可以用特殊的name=参数指定函数导出到R中的R函数名。 如果不指定,R函数名和C++函数名是相同的。
例如
//[[Rcpp::export(name="conv")]]
NumericVector convolveCpp(
NumericVector a, NumericVector b){
......
}
则C++函数convolveCpp导入到R中后, 改名为“conv”。
对于要导出的C++函数, 必须在全局名字空间中定义, 而不能在某个C++名字空间声明内定义。 自变量必须能够用Rcpp::as转换成C++类型, 返回值必须是空值或者能够用Rcpp::wrap转换成R类型。 在自变量和返回值类型说明中, 必须使用完整的类型, 比如std::string不能简写成string。 Rcpp提供的类型如NumericVector可以不必用Rcpp::修饰。
39.3 在R中编译链接C++代码
sourceCpp()
函数可以用code=指定一个R字符串,
字符串的内容是C++源程序,
其中还是用特殊注释//[[Rcpp::export]]
标识要导出的C++函数。 如
sourceCpp(code='
#include <Rcpp.h>
using namespace Rcpp;
//[[Rcpp::export]]
NumericVector convolveCpp(
NumericVector a, NumericVector b){
.........
}
')
convolveCpp(1:3, 1:5)
对于比较简单的单个C++函数,
可以用cppFunction()
函数的code=指定一个R字符串,
字符串的内容是一个C++函数定义, 转换为一个R函数。
例如
cppFunction(code='
int fibonacci(const int x){
if(x < 2) return x;
else
return ( fibonacci(x-1) + fibonacci(x-2) );
}
')
print(fibonacci(5))
为了在R中计算一个简单的C++表达式,
可以用evalCpp(’C++表达式内容’)
,如
函数将返回该C++表达式的值。
在cppFunction()
和evalCpp()
中,
可以用depends=参数指定要链接的其它库,如
在编译代码时与RcppArmadillo的动态连接库连接。
也可以把这样的链接依赖关系写在特殊的C++注释中,如
这样的注释仅对sourceCpp()
和cppFunction()
有效,
在编译R扩展包时,
仍需要把依赖的包列在DESCRIPTION文件的Imports中,
把要链接的包列在LinkingTo中。
39.4 Rcpp属性的其它功能
39.4.1 自变量有缺省值的函数
借助于Rcpp, 自变量有缺省值的C++函数可以自动转换成自变量有缺省值的R函数。 定义时要符合C++语法, 比如带缺省值的自变量都要在不带缺省值的自变量的后面, 缺省值不能有变量。
例如
DataFrame readData(
CharacterVector file,
CharacterVector colNames = CharacterVector::create(),
std::string comment = "#",
bool header = true){ ... }
转换到R中,相当于
39.4.2 允许用户中断
在C++代码中进行长时间的计算时,
应该允许用户可以中断计算。
Rcpp的办法是在C++计算过程中每隔若干步循环就插入一个
Rcpp::checkUserInterrupt();
语句。
39.4.3 把R代码写在C++源文件中
正常情况下,应该把R代码和C++代码写在分别的源程序中, 当C++代码比较短时, 也可以把C++代码写在R源程序中作为一个字符串。
Rcpp允许把C++代码和R代码都写在一个C++源文件中,
R代码作为特殊的注释,以/*** R
行开头,以正常的*/
结束。
在R中用sourceCpp()
调用这个C++源文件,
就可以编译C++后执行其中特殊注释内的R代码。
这样的特殊注释可以有多个。
例如,下述内容保存在文件fibo.cpp
中:
//[[Rcpp::export]]
int fibonacci(const int x){
if(x < 2) return x;
else
return ( fibonacci(x-1) + fibonacci(x-2) );
}
/*** R
# 调用C++中的fibonacci()函数
print(fibonacci(10))
*/
只要在R中运行
就可以编译连接此C++文件,
把其中用//[[Rcpp::export]]
标识的函数转换为R函数,
并在R中执行源文件内特殊注释中的R代码。
39.4.4 在C++中调用R的随机数发生器
在C或C++中调用R的随机数发生器, 需要能够同步地更新随机数发生器状态。 如果利用Rcpp属性编译C++源程序, 则Rcpp属性会自动添加一个RNGScope实例进行随机数发生器状态的同步。