物件的轉換
是時候來仔細的看看前面兩個範例中所使用的Rcpp 物件了:
NumericVector
NumericMatrix
以及一個泛用的轉換函數:
as
這些是和物件轉換相關的Rcpp API。as
的情況比較複雜,所以我們先解釋NumericVector
和NumericMatrix
。
如名稱所述,這兩個物件分別代表著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++ constructor
或 C++ 建構子
。
而Rcpp中,如果要承接來自R的物件,我們只要遵循以下的格式宣告和呼叫constructor即可:
<C++型態名稱> <物件名稱>(<對應的SEXP名稱>);
上述的例子都是遵循這個規則的。NumericVector
和CharacterVector
都是Rcpp中定義的一種C++型態。rs
和x
分別是變數的名稱。而Rx
和Rrs
是來自R的SEXP
物件的名稱。SEXP
物件的名稱是來自於整個Rcpp函數的定義,以及R如何傳遞。我們在物件的傳遞的章節在仔細說明。
而Rcpp在執行constructor的同時,還會檢查附加的SEXP
參數的型態,若型態不對還會補上轉換。這麼多複雜的動作通通都被濃縮在短短的一行之中:
<C++型態名稱> <物件名稱>(<對應的SEXP名稱>);
所以讀者要記得,當從R傳遞物件進入C++的時候,第一步就是正確的呼叫對應的Rcpp物件的constructor,才能使用C++函數對R物件做更進一步的操作。