sourceCpp
函數
前面我們介紹過以 cppFunction
函數將 C++ 程式碼內崁在 R 程式碼中,這樣的方式對於簡短的小程式而言非常方便,但是如果需要開發比較複雜的 C++ 程式時,把大量的 C++ 程式碼貼在 cppFunction
函數中,會讓程式碼難以閱讀,在開發上也會很不方便。
遇到比較複雜的程式架構時,我們可以將 C++ 程式碼獨立出來,另外儲存成一個 .cpp
檔案,然後使用 sourceCpp
函數來編譯並載入 .cpp
檔案中的 C++ 程式碼,這樣的作用跟 cppFunction
函數相同,但是可以讓大型的程式更好管理。
當我們將 C++ 的程式碼獨立出來時,必須引入 Rcpp.h
並且設定 Rcpp
這個 namespace,也就是在檔案的開頭加上這兩行:
#include <Rcpp.h> using namespace Rcpp;
對於那些需要在 R 中被呼叫的 C++ 函數之前,加上這一行註解:
// [[Rcpp::export]]
請注意這一行註解中包含的空白不可以省略。
我們將前面計算總和的 C++ 範例程式改成獨立的一個 cpp_sum.cpp
檔,內容如下:
#include <Rcpp.h> using namespace Rcpp; // [[Rcpp::export]] double cpp_sum(NumericVector x) { int n = x.size(); double total = 0; for(int i = 0; i < n; ++i) { total += x[i]; } return total; }
接著以 sourceCpp
編譯並載入使用:
sourceCpp("cpp_sum.cpp") cpp_sum(1:10)
[1] 55
我們也可以將 R 的程式碼內崁在 C++ 的註解之中,其語法為:
/*** R # 這是 R 的程式碼 */
放在這裡的 R 程式碼會以 source(echo = TRUE)
的方式來執行,所以我們不需要特別以 print
等函數來輸出,這個功能對於開發階段的測試與除錯會有一些幫助。以下是計算總和的範例:
#include <Rcpp.h> using namespace Rcpp; // [[Rcpp::export]] double cpp_sum(NumericVector x) { int n = x.size(); double total = 0; for(int i = 0; i < n; ++i) { total += x[i]; } return total; } /*** R library(microbenchmark) x <- runif(1e3) microbenchmark( sum(x), cpp_sum(x) ) */
將這段 C++ 程式碼儲存在 cpp_sum_embedded_r.cpp
檔案中,然後同樣以 sourceCpp
編譯並載入:
sourceCpp("cpp_sum_embedded_r.cpp")
> library(microbenchmark) > x <- runif(1e3) > microbenchmark( + sum(x), + cpp_sum(x) + ) Unit: microseconds expr min lq mean median uq max neval sum(x) 1.136 1.161 1.53268 1.2245 1.2485 29.916 100 cpp_sum(x) 2.689 2.738 3.12136 2.7840 2.8880 23.875 100
當 C++ 的函數被載入之後,內崁的 R 程式碼也會一併被執行。
範例程式碼
這裡對於一些常見的程式結構提供了 R 與 C++ 的對照範例程式碼,對於 C++ 不熟新的使用者可以參考使用。
if
判斷式
這是使用 if
判斷式計算絕對值的 R 函數:
r_abs <- function(x) { if (x > 0) { x } else { -x } }
C++ 版本則為:
double cpp_abs(double x) { if (x > 0) { return x; } else { return -x; } }
switch
判斷式
這是一段 R 語言的 switch
判斷式範例程式碼:
r_int2str <- function(x) { switch(as.character(x), "1" = "one", "2" = "two", "oops" ) }
C++ 版本則為:
std::string cpp_int2str(int x) { switch(x) { case 1: return "one"; case 2: return "two"; default: return "oops"; } }
for
迴圈
這是使用 for
迴圈計算平均值的 R 函數:
r_mean <- function(x) { n = length(x) total = 0 for(i in 1:n) { total <- total + x[i] } total / n }
C++ 版本則為:
double cpp_mean(NumericVector x) { int n = x.size(); double total = 0; for(int i = 0; i < n; ++i) { total += x[i]; } return total / n; }
while
迴圈
這是使用 while
迴圈計算階乘的 R 函數:
r_factorial <- function(x) { result = x while (x > 2) { x <- x - 1 result <- result * x } result }
C++ 版本則為:
int cpp_factorial(int x) { int result = x; while (x > 2) { x -= 1; result *= x; } return result; }
next
與 break
這是使用 next
與 break
列出奇數的 R 函數:
r_odd <- function(x) { i <- 0 result = integer() while ( TRUE ) { i <- i + 1 if (i > x) break if (i %% 2 == 0) next result = c(result, i) } result }
C++ 版本則為:
IntegerVector cpp_odd(int x) { int i = 0; IntegerVector result; while ( true ) { i += 1; if (i > x) break; if (i % 2 == 0) continue; result.push_back(i); } return result; }
IntegerVector
的 push_back
跟 std::vector
的 push_back
用法相同,可將新元素從後方加入向量中。