boost logo
Boost 多維數組庫(Boost.MultiArray)

摘要

Boost 多維數組庫提供了一個多維數組的類模板,即由連續數據組成的多個數組的語義等價的適配器。本庫中的類實現了一個公共的接口,形式化為一個泛型的編程概念。 這個接口的設計符合C++標準庫的容器的要求。Boost MultiArray 比其它已有的類似實現(特別是用 std::vector<std::vector<...>> 來模擬的N維數組)更高效也更方便。本庫提供的數組可以用類似於C++原生數組的語法來訪問。還具有其它特性,如改變大小、整形和創建視圖(具體見後)。

目錄

  1. 簡介
  2. 簡短的例子
  3. MultiArray 組件
  4. 構造與賦值
  5. 數組視圖與子數組類型生成器
  6. 指定數組的維度
  7. 訪問元素
  8. 創建視圖
  9. 存儲的順序
  10. 設置數組的基數
  11. 改變數組的形狀
  12. 改變數組的大小
  13. MultiArray 概念
  14. 測試案例
  15. 相關工作
  16. 感謝

簡介

C++ 標準庫提供了幾種泛型容器,但是沒有提供多維數組類型。std::vector 類模板可以用於實現N維數組,例如一個 double 元素的2維數組可以用類型 std::vector<std::vector<double>> 來表示,但是得到的接口是難以使用的,而且內存的開銷也有點大。原生的C++數組(即 int arr[2][2][2];)則不可以直接與C++標準庫進行交互,而且在函數調用綁定時會丟失信息(主要是最後一維的長度)。最後,動態分配的一段連續內存塊可以用作一個數組,但是這種方法要求手工簿記的工作,這是很容易出錯且擾亂了程序員的意圖。

Boost MultiArray 庫為C++標準庫容器增加了通用的多維數組抽像。它包含一個泛型的數組類模板和原生數組的適配器,支持慣用的數組操作,可以與C++標準庫的容器以及算法 進行交互。這些數組共享一個公共的接口,作為一個泛型編程接口,泛型數組算法可以以之作為依據。

本文檔打算對最基本和最常用的 MultiArray 組件提供一個介紹性的教程和用戶指南。參考手冊 則提供了更為完整和正式的庫文檔。

簡短的例子

以下是使用 multi_array 的一個簡短例子:
#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;
}

MultiArray 組件

Boost.MultiArray 的實現(boost/multi_array.hpp)提供了三個用戶級類模板:
  1. multi_array,
  2. multi_array_ref, 以及
  3. const_multi_array_ref
multi_array 是一個容器模板。在初始化時,它根據構造時所指定的元素數量分配空間。multi_array 也可以缺省構造並在需要時改變其大小。

multi_array_ref 對一個已有的數組進行適配以提供 multi_array 的接口。multi_array_ref 並不擁有傳遞給它的數據。

const_multi_array_ref 類似於 multi_array_ref,但保證數組中的內容不會被改變。因此它可以包裝類型 T const* 的指針。

這三個組件的行為非常相似。除了構造函數的參數以外,multi_arraymulti_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, 可以作為來源複製到另一個形狀匹配的數組。複製的結果是對數組中的數據進行深複製(逐個元素進行)。

數組視圖與子數組類型生成器

在某些情況下,使用嵌套的 array_view 和 subarray 類型生成器不太方便。例如,在一個依據數組類型進行參數化的函數模板中,額外的 "template" 關鍵字可能使人迷糊。更有甚者,有些編譯器不能處理模板參數中的嵌套模板。因此,我們提供了類型生成器 subarray_gen, const_subarray_gen, array_view_gen, 和 const_array_view_gen. 這樣,以下例子中的兩個 typedef 將得到相同的類型:
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;
// ...
}

指定數組的維度

多數 Boost.MultiArray 組件在創建時,都需要指定數組的維數以及每個維度的長度(boost::multi_array 也提供了缺省構造函數)。其中維度是作為一個模板參數來指定的,而指定每個維度的長度則有兩種不同的機制。

第一種方法是將一個包含各維長度的 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]);

訪問元素

Boost.MultiArray 組件提供了兩種方法來訪問容器中的指定元素。第一種方法使用了傳統C數組的方式,由 operator[] 提供訪問。

例子

  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);

第二種方法則是傳遞一個索引的 Collectionoperator(). 本庫將從中取出N個索引作為容器的N個維度。

例子

  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);
這種方法有助於編寫維度無關的代碼,而且在某些編譯器中可以生成比 operator[] 更高性能的代碼。

缺省地,以上兩種元素訪問方法均執行範圍檢查。如果提供的某個索引超出數組預定義的範圍,將會引發一個斷言而退出程序。要關閉範圍檢查(以提高產品發佈版本的性能),需要在應用程序包含 multi_array.hpp 之前定義 BOOST_DISABLE_ASSERTS 預處理器宏。

創建視圖

Boost.MultiArray 提供了從已有的數組組件創建一個子視圖的工具。你可以創建一個保持相同維數的子視圖,也可以創建一個維數比原來少的子視圖。

子視圖的創建由一個對 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 的更多信息

index_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;
傳給一個切片操作的 index_range 對象,如果你沒有指定其開始值或結束值,它將從進行切片的數組繼承。這種便利性讓你在特定情形下無需知道數組各維度的邊界。例如,缺省構造的 range 與用它來指定的維度具有相同的長度。

例子

  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的數組存儲順序不同的遺留代碼進行接口時非常有用,如 FORTRAN. 可選的值有 c_storage_order, fortran_storage_order, 和 general_storage_order.

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));

設置數組的基數

在某些情況下,使用從零起計的數組可能不太方便或者難以使用。Boost.MultiArray 的組件提供了兩個方法來修改數組的基數。一種方法是向 extent_gen 構造函數指定一對範圍值來設置基數。

例子

  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.

Example

  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);

改變數組的形狀

Boost.MultiArray 數組提供了整形操作。只要維數和元素數量保持不變,數組的形狀改變。

例子

  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);

注意,對一個數組進行整形不會影響索引。

改變數組的大小

boost::multi_array 類提供了保留元素的調整大小操作。維數必須保持一致,但每個維度的長度可以按需要增加或減少。當一個數組被擴大時,原有元素將被複製到新的內存中,然後舊 內存中的元素將被析構。而數組中的新元素將是缺省構造的。但是,如果新數組的某些維度的大小是縮短的,則有些元素將不再可用。

例子

  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] 不再有效

MultiArray 概念

Boost.MultiArray 定義並使用了 MultiArray 概念。它給定了N維容器的一個接口。

測試案例

Boost.MultiArray 帶有一組測試案例,用於測試本庫的特性和語義。測試案例的相關說明請看 這裡

相關工作

boost::arraystd::vector 都是一維的容器。均管理了它們自己的內存。std::valarray 是一個低級的C++標準庫組件,為數學應用提供了可移植的高性能。Blitz++ 是由 Todd Veldhuizen 開發的一個數組庫。它使用了高級的C++技術來為基於數組的數學應用提供接近於Fortran的性能。array_traits 則是先前由Boost分發的一個測試庫,提供了在原生C++數組上創建迭代器的方法。該庫類似於 boost::array,增強了C風格的N維數組,就像 boost::array 為C的一維數組所作的那樣。

感謝


Ronald Garcia
Last modified: Tue Feb 7 17:15:50 EST 2006