1 min read

Rcpp Tutorial Chapter 3

物件的轉換

是時候來仔細的看看前面兩個範例中所使用的Rcpp 物件了:

  • NumericVector
  • NumericMatrix

以及一個泛用的轉換函數:

  • as

這些是和物件轉換相關的Rcpp API。as的情況比較複雜,所以我們先解釋NumericVectorNumericMatrix。 如名稱所述,這兩個物件分別代表著R之中的numeric型態的向量和矩陣。

Rcpp中物件的名稱是經過設計的,讀者在累積足夠的知識後,應該從名稱就可以猜到Rcpp物件是對應到哪一種類型的R物件了。

在更進一步解釋之前,我們需要先了解物件的型態

物件的型態(type)

由於使用R的時候,R會自動判斷物件的型態,所以R的使用者可能不清楚什麼是型態

所有物件的資料,最終就是電腦記憶體中的0和1(又稱做bit),而電腦要怎麼解釋這些0和1的意義?

舉例來說,00110000這8個bit可以解釋為文字符號"0",也可以解釋為整數48

而型態就是電腦解釋這些bit的方式。

如果對應到實際的世界:如果有一匹馬的名稱叫作小明,那小明的型態就是馬,而小明則是小明這隻馬的名稱。

在程式中常見的基礎型態是整數、數值(實數)、字串或boolean,而他們在R和C++中有不一樣的名字:

  • 整數型態在R叫integer,C++叫int
  • 數值型態在R叫numeric,C++叫double
  • 字串型態在R叫character,C++叫std::string*。先不要管那個::,就把std::string當成一個型態的名稱就好!
  • boolean型態在R叫logical,C++叫bool

在R 之中還另外有一種稱為raw的向量,主要是用於儲存特殊格式的資料。它的概念近似於一般資料庫系統中的BLOB(Binary Large OBject)物件。

備註: 字串型態在C++中有點複雜,詳細解釋的話超過本章節的範圍了。所以簡單起見,我一律用std::string來代表。

在R 的世界中,R 會自動判斷物件的型態,所以使用者並不需要有這方面的知識,就可以用R了。

但是在C++的世界中,所有物件的型態都要非常清楚。所以在用Rcpp的時候,我們需要初步的了解R 物件的型態。實際上,只要熟悉上面提到的4個型態,就可以將Rcpp應用在很廣泛的問題了!所以讀者不用感到害怕。

從R 到 C++

不透過Rcpp的話,在C++中,所有R的物件都是型態為SEXP的物件,這物件包含了另外三種pointer來供R處理各種型態。因此我們不能直接對SEXP做操作,必須要透過Rcpp所提供的物件和API。而且對於不同的R型態,必須要透過對應的Rcpp物件才能正確的處理。

而Rcpp中對於物件的命名也是有下一番心思的。舉例來說,讀者在R執行:

class(letters)
## [1] "character"

可以了解letters的型態是character,所以它在Rcpp中的就是用CharacterVector來處理。規則就是:把R的型態名稱改成大寫開頭,後面接上Vector即可。這裡Vector表示這是一個向量物件。在R中,所有物件都是向量,所以這就是R最基本的型態。

同樣的道理:

class(1:5)
## [1] "integer"

所以1:5這個物件在Rcpp中就是透過IntegerVector來處理。

我要再次強調,唯有透過對應的Rcpp物件才能正確的在C++中處理R物件。我們用inline來跑個簡單的Demo:

library(Rcpp);library(inline)
## Error in library(inline): there is no package called 'inline'
f <- cxxfunction(sig=c(Rx="integer"), plugin="Rcpp", body='
  IntegerVector x(Rx);
  return x;
')
## Error in cxxfunction(sig = c(Rx = "integer"), plugin = "Rcpp", body = "\n  IntegerVector x(Rx);\n  return x;\n"): could not find function "cxxfunction"
f(1:5)
## Error in f(1:5): could not find function "f"
f(pi)
## Error in f(pi): could not find function "f"
f(letters)
## Error in f(letters): could not find function "f"

ps. 由於IntegerVector是Rcpp提供的物件,所以可以回傳到R之中。

  • 1:5 本身就是integer,所以進入f沒有任何問題。
  • pi本身是numeric,所以進入f後變成了3。當物件型態不吻合IntegerVector時,Rcpp會先嘗試呼叫as.integer來做型態轉換,再把他轉成IntegerVector。所以這裡pi就變成3了。
  • letters是無法轉換為integer的character型態的物件。所以Rcpp直接拋出錯誤給R。

所以請讀者務必要清楚對應的物件關係。

就如同一般C/C++中有SEXP處理所有的R物件,Rcpp之中也可以使用RObject(注意:第二個字母也是大寫!)型態來處理所有的R物件。而除了Vector,Rcpp 也提供了Matrix類型的物件型態(如NumericMatrix)。使用起來和Vector類似並且多了row, column和dimension的API。這類的應用比較深,在本章節就先介紹了,留到後頭的範例再跟讀者介紹它們的用法。

*Rcpp物件的繼承

不懂繼承的讀者可以跳過這一節。

Rcpp中所有的物件都是繼承自RObject。我們可以從Rcpp的官方Reference中看到Rcpp中物件的繼承結構。

Matrix類型也繼承自對應的Vector類型,就像是在R之中matrix物件本質上也是vector是一樣的。

C++ Constructor

接下來我們該解釋之前例子中的:

  NumericVector rs(Rrs);

以及剛剛例子中的:

  CharacterVector x(Rx);

這種語法的意義了。

在C++中,一個物件要透過constructor建構後才會有實際的資料。詳細解釋constructor的概念已經超出本章範圍,所以我建議讀者如果有興趣的話,可以自行google C++ constructorC++ 建構子

而Rcpp中,如果要承接來自R的物件,我們只要遵循以下的格式宣告和呼叫constructor即可:

<C++型態名稱> <物件名稱>(<對應的SEXP名稱>);

上述的例子都是遵循這個規則的。NumericVectorCharacterVector都是Rcpp中定義的一種C++型態。rsx分別是變數的名稱。而RxRrs是來自R的SEXP物件的名稱。SEXP物件的名稱是來自於整個Rcpp函數的定義,以及R如何傳遞。我們在物件的傳遞的章節在仔細說明。

而Rcpp在執行constructor的同時,還會檢查附加的SEXP參數的型態,若型態不對還會補上轉換。這麼多複雜的動作通通都被濃縮在短短的一行之中:

<C++型態名稱> <物件名稱>(<對應的SEXP名稱>);

所以讀者要記得,當從R傳遞物件進入C++的時候,第一步就是正確的呼叫對應的Rcpp物件的constructor,才能使用C++函數對R物件做更進一步的操作。

*其他的constructor用法

*記憶體的管理