Boost 多維數組庫提供了一個多維數組的類模板,即由連續數據組成的多個數組的語義等價的適配器。本庫中的類實現了一個公共的接口,形式化為一個泛型的編程概念。 這個接口的設計符合C++標準庫的容器的要求。Boost MultiArray 比其它已有的類似實現(特別是用 std::vector<std::vector<...>> 來模擬的N維數組)更高效也更方便。本庫提供的數組可以用類似於C++原生數組的語法來訪問。還具有其它特性,如改變大小、整形和創建視圖(具體見後)。
C++ 標準庫提供了幾種泛型容器,但是沒有提供多維數組類型。std::vector 類模板可以用於實現N維數組,例如一個 double 元素的2維數組可以用類型 std::vector<std::vector<double>> 來表示,但是得到的接口是難以使用的,而且內存的開銷也有點大。原生的C++數組(即 int arr[2][2][2];)則不可以直接與C++標準庫進行交互,而且在函數調用綁定時會丟失信息(主要是最後一維的長度)。最後,動態分配的一段連續內存塊可以用作一個數組,但是這種方法要求手工簿記的工作,這是很容易出錯且擾亂了程序員的意圖。
Boost MultiArray 庫為C++標準庫容器增加了通用的多維數組抽像。它包含一個泛型的數組類模板和原生數組的適配器,支持慣用的數組操作,可以與C++標準庫的容器以及算法 進行交互。這些數組共享一個公共的接口,作為一個泛型編程接口,泛型數組算法可以以之作為依據。
本文檔打算對最基本和最常用的 MultiArray 組件提供一個介紹性的教程和用戶指南。參考手冊 則提供了更為完整和正式的庫文檔。
#include "boost/multi_array.hpp"
#include <cassert>
int
main () {
// 創建一個 3 x 4 x 2 的 3D 數組
typedef boost::multi_array<double, 3> array_type;
typedef array_type::index index;
array_type A(boost::extents[3][4][2]);
// 賦值到數組的元素
int values = 0;
for(index i = 0; i != 3; ++i)
for(index j = 0; j != 4; ++j)
for(index k = 0; k != 2; ++k)
A[i][j][k] = values++;
// 校驗元素值
int verify = 0;
for(index i = 0; i != 3; ++i)
for(index j = 0; j != 4; ++j)
for(index k = 0; k != 2; ++k)
assert(A[i][j][k] == verify++);
return 0;
}
multi_array_ref 對一個已有的數組進行適配以提供 multi_array 的接口。multi_array_ref 並不擁有傳遞給它的數據。
const_multi_array_ref 類似於 multi_array_ref,但保證數組中的內容不會被改變。因此它可以包裝類型 T const* 的指針。
這三個組件的行為非常相似。除了構造函數的參數以外,multi_array 和 multi_array_ref 具有相同的接口。const_multi_array_ref 只提供了 multi_array_ref 的接口中具有常量性的部分。
每一個數組類型 - multi_array, multi_array_ref, 和 const_multi_array_ref - 提供了一組專門的構造函數。更進一步的信息,請見相關參考。
本庫中所有非常量性的數組類型都提供了賦值操作符 operator=(). 數組類型 multi_array, multi_array_ref, subarray, 以及 array_view 中的每一個都可以從另一個進行賦值,只要它們的形狀匹配。它們的 const 變體,包括 const_multi_array_ref, const_subarray, 和 const_array_view, 可以作為來源複製到另一個形狀匹配的數組。複製的結果是對數組中的數據進行深複製(逐個元素進行)。
template <typename Array>
void my_function() {
typedef typename Array::template array_view<3>::type view1_t;
typedef typename boost::array_view_gen<Array,3>::type view2_t;
// ...
}
第一種方法是將一個包含各維長度的 Collection 傳遞給構造函數,最常用的是 boost::array. 構造函數將從容器中找到始端迭代器並取出N個元素,將這N個元素對應為N個維度的長度。這一方法有助於編寫維度無關的代碼。
typedef boost::multi_array<double, 3> array_type;
boost::array<array_type::index, 3> shape = {{ 3, 4, 2 }};
array_type A(shape);
第二種方法則是傳遞給構造函數一個 extent_gen 對象,以指定矩陣的各個維度。缺省地,本庫構造一個全局 extent_gen 對像 boost::extents. 如果你關心這些對像所佔用的內存,可以在包含本庫的頭文件之前定義 BOOST_MULTI_ARRAY_NO_GENERATORS 以禁止該對象的構造。
typedef boost::multi_array<double, 3> array_type;
array_type A(boost::extents[3][4][2]);
typedef boost::multi_array<double, 3> array_type;
array_type A(boost::extents[3][4][2]);
A[0][0][0] = 3.14;
assert(A[0][0][0] == 3.14);
第二種方法則是傳遞一個索引的 Collection 給 operator(). 本庫將從中取出N個索引作為容器的N個維度。
這種方法有助於編寫維度無關的代碼,而且在某些編譯器中可以生成比 operator[] 更高性能的代碼。typedef boost::multi_array<double, 3> array_type;
array_type A(boost::extents[3][4][2]);
boost::array<array_type::index,3> idx = {{0,0,0}};
A(idx) = 3.14;
assert(A(idx) == 3.14);
缺省地,以上兩種元素訪問方法均執行範圍檢查。如果提供的某個索引超出數組預定義的範圍,將會引發一個斷言而退出程序。要關閉範圍檢查(以提高產品發佈版本的性能),需要在應用程序包含 multi_array.hpp 之前定義 BOOST_DISABLE_ASSERTS 預處理器宏。
子視圖的創建由一個對 operator[] 的調用進行,傳入一個 index_gen 類型。index_gen 通過傳遞 index_range 對像給它的 operator[] 來組裝。類似於 boost::extents, 本庫缺省情況下會構造對像 boost::indices. 你可以通過在包含本庫的頭文件之前定義 BOOST_MULTI_ARRAY_NO_GENERATORS 來禁止這一對象的構造。以下是一個簡單的子視圖創建例子。
// myarray = 2 x 3 x 4
//
// array_view 的各個維度: [base,bound) (缺省的維度步幅 = 1)
// dim 0: [0,2)
// dim 1: [1,3)
// dim 2: [0,4) (步幅為 2),
//
typedef array_type::index_range range;
array_type::array_view<3>::type myview =
myarray[ boost::indices[range(0,2)][range(1,3)][range(0,4,2)] ];
for (array_type::index i = 0; i != 2; ++i)
for (array_type::index j = 0; j != 2; ++j)
for (array_type::index k = 0; k != 2; ++k)
assert(myview[i][j][k] == myarray[i][j+1][k*2]);
通過傳遞一個整數值給 index_gen, 你可以創建一個比原有數組的維度更少的子視圖(也稱為切片)。
// myarray = 2 x 3 x 4
//
// array_view 的各個維度:
// [base,stride,bound)
// [0,1,2), 1, [0,2,4)
//
typedef array_type::index_range range;
array_type::index_gen indices;
array_type::array_view<2>::type myview =
myarray[ indices[range(0,2)][1][range(0,4,2)] ];
for (array_type::index i = 0; i != 2; ++i)
for (array_type::index j = 0; j != 2; ++j)
assert(myview[i][j] == myarray[i][1][j*2]);
傳給一個切片操作的 index_range 對象,如果你沒有指定其開始值或結束值,它將從進行切片的數組繼承。這種便利性讓你在特定情形下無需知道數組各維度的邊界。例如,缺省構造的 range 與用它來指定的維度具有相同的長度。// [base,stride,bound)
// [0,2,4)
typedef array_type::index_range range;
range a_range;
a_range = range(0,4,2);
a_range = range().start(0).finish(4).stride(2);
a_range = range().start(0).stride(2).finish(4);
a_range = 0 <= range().stride(2) < 4;
a_range = 0 <= range().stride(2) <= 3;
以下切片操作展示另一種方式:typedef array_type::index_range range;
range a_range;
// 該維度的所有元素
a_range = range();
// 索引 i 滿足 3 <= i
a_range = range().start(3)
a_range = 3 <= range();
a_range = 2 < range();
// 索引 i 滿足 i < 7
a_range = range().finish(7)
a_range = range() < 7;
a_range = range() <= 6;
// 包含維度1的所有元素
// 維度2中滿足 i < 5 的元素
// 維3中滿足 4 <= j <= 7 且步幅為 2 的元素
myarray[ boost::indices[range()][range() < 5 ][4 <= range().stride(2) <= 7] ];
c_storage_order 是缺省值,它將以C數組相同的順序在內存中存儲各元素,即從後往前保存各個維度。
fortran_storage_order 則以 FORTRAN 的順序在內存中存儲各個元素:從第一個維度到最後一個。注意,在使用這個參數時,數組的索引仍然保持為從零起計。
typedef boost::multi_array<double,3> array_type;
array_type A(boost::extents[3][4][2],boost::fortran_storage_order);
call_fortran_function(A.data());
general_storage_order 允許你定制在內存中保存各個維度的順序,以及各個維度是按升序還是降序來保存。
typedef boost::general_storage_order<3> storage;
typedef boost::multi_array<int,3> array_type;
// 先保存最後一個維度,然後是第一個維度,最後是中間
array_type::size_type ordering[] = {2,0,1};
// 以降序保存第一個維度(維度0)
bool ascending[] = {false,true,true};
array_type A(extents[3][4][2],storage(ordering,ascending));
typedef boost::multi_array<double, 3> array_type;
typedef array_type::extent_range range;
array_type::extent_gen extents;
// 維度 0: 基數為 0
// 維度 1: 基數為 1
// 維度 2: 基數為 -1
array_type A(extents[2][range(1,4)][range(-1,3)]);
另一種方法是,首先以普通方式構造數組,然後再重設基數。要將所有基數設為相同的值,可以使用 reindex 成員函數,傳入一個新的索引基數。
typedef boost::multi_array<double, 3> array_type;
typedef array_type::extent_range range;
array_type::extent_gen extents;
array_type A(extents[2][3][4]);
// 改為以 1 為基數
A.reindex(1)
還有一種方法是用 reindex 成員函數分別設置每個維度的基數,傳入一個索引基數的 Collection.
typedef boost::multi_array<double, 3> array_type;
typedef array_type::extent_range range;
array_type::extent_gen extents;
// 維度 0: 基數為 0
// 維度 1: 基數為 1
// 維度 2: 基數為 -1
array_type A(extents[2][3][4]);
boost::array<array_type::index,ndims> bases = {{0, 1, -1}};
A.reindex(bases);
typedef boost::multi_array<double, 3> array_type;
typedef array_type::extent_range range;
array_type::extent_gen extents;
array_type A(extents[2][3][4]);
boost::array<array_type::index,ndims> dims = {{4, 3, 2}};
A.reshape(dims);
typedef boost::multi_array<int, 3> array_type;
typedef array_type::extent_range range;
array_type::extent_gen extents;
array_type A(extents[3][3][3]);
A[0][0][0] = 4;
A[2][2][2] = 5;
A.resize(extents[2][3][4]);
assert(A[0][0][0] == 4);
// A[2][2][2] 不再有效