Chapter 30 標準 C++ のデータ構造を利用する

標準 C++ では vector list map set などの様々なデータ構造(コンテナ)が提供されています。それらはデータへのアクセス・追加・削除などの効率が異なるので、目的に応じて使い分けることで、実現したい処理をより効率よく実装できる場合があります。

例えば、ベクトルに要素を追加する処理を例にすると、Rcpp の Rcpp::Vector と標準 C++ の std::vector には、どちらにもベクトルの末尾に要素を追加するメンバ関数 push_back() が提供されていますが、その処理効率には大きな違いがあります。なぜなら Rcpp::Vector では push_back() メンバ関数を実行する度に追加した値を含むベクトル全体の値をメモリ上の他の場所にコピーする処理が発生するのに対して、std::vector では多くの場合には全体のコピーを行うことなく末尾に要素を追加することができるためです。

下に、std::vector を用いた例として、行列の要素の値が 0 ではない要素の行番号と列番号を取得する例を示します。

下に、いくつかの主要な標準 C++ データ構造の概要を示します。

標準 C++ データ構造 概要
vector 可変長配列:各要素はメモリ上で連続して配置されます。
list 可変長配列:各要素はメモリ上で分散して配置されます。
map, unordered_map 連想配列:キー・バリュー形式でデータを保持します。
set, unordered_set 集合:重複のない値の集合を保持します。

map は要素がキーの値でソートされた順に並びます。それに対して unordered_map では順番は保証されませんが要素の挿入とアクセスの速度に優ります。同様に、set は要素の値でソートされた順に並びます。unordered_set では順番は保証されませんが要素の挿入とアクセスの速度に優ります。

30.1 標準 C++ データ構造と Rcpp データ構造の変換

Rcpp のデータ構造と標準 C++ のデータ構造の変換には as<T>() 関数と wrap() 関数を用います。

  • as<CPP>(RCPP) : Rcpp データ構造(RCPP)を標準 C++ データ構造(CPP)に変換します
  • wrap(CPP) : 標準 C++ データ構造(CPP)を Rcpp データ構造に変換します

下表に Rcpp と標準 C++ で変換可能なデータ構造の対応を示します。(+ は対応している、- 対応していないことを示しています。)

Rcpp 標準 C++ as wrap
Vector vector, list, deque + +
List, DataFrame vector<vector>, list<vector> など + +
名前付き Vector map, unordered_map - +
Vector set, unordered_set - +

次のコード例では、Rcpp の Vector と標準 C++ のシーケンス・コンテナ( vector, list, deque など値が直列に並んでいるように扱えるコンテナ)を変換する例を示します。

次のコード例では、標準 C++ のシーケンス・コンテナが入れ子になった2次元コンテナを DataFrameList に変換する例を示します。

次のコード例では、標準 C++ の map<key, value>unordered_map<key, value>key を要素の名前、value を要素の型とした、名前付き Vector に変換されることを示します。

実行結果

std::map ではキーの値でソートされているのに対して、std::unordered_map では順番が保証されないことがわかります。

> std_map()
[[1]]
A B C
2 1 3

[[2]]
A C B
2 3 1

30.2 標準 C++ データ構造を関数の引数や返値にする

as() 関数や wrap() 関数で変換可能な標準 C++ データ構造は Rcpp 関数の引数や返値にすることもできます。そこでは R から Rcpp で記述した関数に値が渡される時、暗黙的に as() が呼ばれ、関数の返値が R に戻されるときには暗黙的に wrap() が呼ばれてデータが変換されます。