物件的轉換

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

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

這些是和物件轉換相關的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 之中還另外有一種稱為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)
## Attaching package: 'inline'
## The following object is masked from 'package:Rcpp':
## 
## registerPlugin
f <- cxxfunction(sig = c(Rx = "integer"), plugin = "Rcpp", body = "\nIntegerVector x(Rx);\nreturn x;\n")
f(1:5)
## [1] 1 2 3 4 5
f(pi)
## [1] 3
f(letters)
## Error: not compatible with INTSXP

ps. 由於IntegerVector是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物件做更進一步的操作。

*深入探討的Vector

在Rcpp中,所有Vector類的R物件都是透過Vector<RTYPE>這個template class來處理。

這裡的RTYPE則是一個標記SEXP實際的資料類別的整數,他的值可以在R本體提供的C API中的Rinternal.h中查到。這裡我列一些常用的:

C的macro 類別
1 INTSXP 13 integer vectors
2 REALSXP 14 real variables
3 CPLXSXP 15 complex variables
4 STRSXP 16 string vectors
5 DOTSXP 17 dot-dot-dot object(…)
6 VECSXP 19 generic vectors(list)
7 EXPRSXP 20 expressions vectors
8 BCODESXP 21 byte code
9 EXTPTRSXP 22 external pointer
10 WEAKREFSXP 23 weak reference
11 RAWSXP 24 raw bytes
12 S4SXP 25 S4 non-vector

透過template的技術,Rcpp只需要實作好Vector<RTYPE>,再透過以下語法定義我們剛剛介紹的IntegerVectorNumericVector

typedef Vector<INTSXP> IntegerVector;
typedef Vector<REALSXP> NumericVector;

因此,這類型的class都可以查閱Rcpp Reference: Vector來了解它的細節。

以0.10.4版本的API為例,我們可以看到一系列的繼承關係:

*記憶體的管理