多維數組
這個特性用于訪問多維數組,之前C++ operator[] 只支持訪問單個下標,無法訪問多維數組。
因此要訪問多維數組,以前的方式是:
- 重載operator(),于是能夠以m(1, 2) 來訪問第1 行第2 個元素。但這種方式容易和函數調
用產生混淆; - 重載operator[],并以std::initializer_list 作為參數,然后便能以m[{1, 2}] 來訪問元
素。但這種方式看著別扭; - 鏈式鏈接operator[],然后就能夠以m[1][2] 來訪問元素。C語言二維數組的訪問方式;
- 定義一個at() 成員,然后通過at(1, 2) 訪問元素。同樣不方便。
在C++23,我們終于可以通過m[1, 2] 這種方式來訪問多維數組。一個例子
template <class T, size_t R, size_t C>
struct matrix
{T& operator[](const size_t r, const size_t c) noexcept {return data_[r * C + c];}const T& operator[](const size_t r, const size_t c) const noexcept {return data_[r * C + c];}private:
std::array<T, R * C> data_;
};int main()
{matrix<int, 2, 2> m;m[0, 0] = 0;m[0, 1] = 1;m[1, 0] = 2;m[1, 1] = 3;for (auto i = 0; i < 2; ++i) {for (auto j = 0; j < 2; ++j) {std::cout << m[i, j] << ' ';}std::cout << std::endl;}
}
std::mdspan
std::mdspan
(多維數組視圖)是 C++23 新增的非擁有多維數組的視圖,用于表示連續對象序列,允許靈活操作多維數據,支持動態維度。?這個連續對象序列可以是一個簡單的?C 數組、帶有大小的指針、std::array
、std::vector
?或?std::string
。mdspan
?是一種輕量級的多維數組視圖,不持有數據,而是提供了對現有數據的多維訪問方式。它結合了指針和多維索引的優點,使得數據訪問更加高效和靈活。
在標頭? | ||
template< ? ??class?T, |
模板形參
T | - | 元素類型;既不是抽象類也不是數組類型的完整對象類型。 |
Extents | - | 指定維數及各維大小,均為編譯時已知。必須是?std::extents?的特化。 |
LayoutPolicy | - | 指定如何將多維索引轉換為底層的一維索引(列優先三維數組、對稱三角二維矩陣等)。必須滿足布局映射策略?(LayoutMappingPolicy)?。 |
AccessorPolicy | - | 指定如何將底層一維索引轉換為對 T 的引用。必須滿足?std::is_same_v<T,?typename?AccessorPolicy?::?element_type>?為?true?的約束條件。必須滿足訪問器策略?(AccessorPolicy) |
由于 C++17 中的類模板參數推導(CTAD),編譯器通常可以自動從初始化器的類型推導出模板參數。
成員函數
(構造函數) | 構造一個?mdspan (公開成員函數) |
operator= | 給一個?mdspan ?賦值(公開成員函數) |
元素訪問 | |
operator[] | 訪問指定多維索引處的元素 (公開成員函數) |
觀察器 | |
size | 返回多維索引空間的大小 (公開成員函數) |
empty | 檢查索引空間大小是否為零 (公開成員函數) |
stride | 獲取沿指定維度的步長 (公開成員函數) |
extents | 獲取范圍(extent)對象 (公開成員函數) |
data_handle | 獲取指向底層一維序列的指針 (公開成員函數) |
mapping | 獲取映射(mapping)對象 (公開成員函數) |
accessor | 獲取訪問器策略對象 (公開成員函數) |
is_unique | 確定此 mdspan 的映射是否唯一(每個索引組合映射到不同的基礎元素) (公開成員函數) |
is_exhaustive | 確定此 mdspan 的映射是否詳盡(exhaustive)(可以使用某些索引組合訪問每個底層元素) (公開成員函數) |
is_strided | 確定此 mdspan 的映射是否跨步(在每個維度中,每次遞增索引都會跳過相同數量的基礎元素) (公開成員函數) |
is_always_unique [靜態] | 確定此 mdspan 的布局映射是否始終唯一(unique) (公開靜態成員函數) |
is_always_exhaustive [靜態] | 確定此 mdspan 的布局映射(layout mapping)是否總是詳盡的 (公開靜態成員函數) |
is_always_strided [靜態] | 確定此 mdspan 的布局映射是否始終跨步(strided) (公開靜態成員函數) |
非成員函數
std::swap(std::mdspan) (C++23) | 針對 mdspan 特化的?std::swap?算法 (函數模板) |
子視圖 | |
submdspan (C++26) | 返回現存?mdspan ?的子集上的視圖(函數模板) |
submdspan_extents (C++26) | 從現存 extents 和分片說明符創建新的 extents (函數模板) |
輔助類型和模板
extents (C++23) | 某秩多維索引空間的一個描述符 (類模板) |
dextentsdims (C++23)(C++26) | 全動態?std::extents?的方便別名模板 (別名模板) |
default_accessor (C++23) | 指示索引訪問?mdspan ?元素的方式的類型(類模板) |
aligned_accessor (C++26) | 提供按對齊訪問?mdspan ?成員的類型(類模板) |
布局映射策略 | |
layout_left (C++23) | 列優先多維數組布局映射策略;最左邊的尺度具有步幅?1 (類) |
layout_right (C++23) | 行優先多維數組布局映射策略;最右邊的尺度具有步幅?1 (類) |
layout_stride (C++23) | 具有用戶自定義步長的布局映射策略 (類) |
layout_left_padded (C++26) | 具有可大于或等于最左側尺度的填充跨步的列主序布局映射策略 (類) |
layout_right_padded (C++26) | 具有可大于或等于最右側尺度的填充跨步的行主序布局映射策略 (類) |
子視圖輔助項 | |
full_extentfull_extent_t (C++26) | 切片說明符標簽,描述指定尺度的全部索引范圍。 (標簽) |
strided_slice (C++26) | 切片說明符,表示一組按照偏移量、尺度和跨步三值指定的有規律分布的索引 (類模板) |
submdspan_mapping_result (C++26) | 各?submdspan_mapping ?重載的返回類型(類模板) |
示例 1:基本使用
#include<iostream>
#include <mdspan>int main() {// 一維連續存儲(模擬二維數組)int data[] = {1,2,3,4,5,6,7,8,9,10,11,12};// 創建 3x4 的二維視圖std::mdspan mat(data, 3, 4); // 訪問元素 [行][列]std::cout << mat[1][2]; // 輸出 7// 修改元素mat[2][3] = 42; // 修改第3行第4列的元素
}
示例 2:動態維度?
#include <mdspan>
#include <vector>int main() {std::vector<int> data(20); // 動態存儲// 創建 4x5 的動態視圖std::mdspan<int, std::dextents<2>> dyn_span(data.data(), 4, 5);dyn_span[3][4] = 100; // 最后一元素
}
3. 多維數組的布局策略
mdspan
?允許指定內存布局,優化訪問模式:
-
行優先(C風格):
std::layout_right
(默認) -
列優先(Fortran風格):
std::layout_left
#include <mdspan>int main() {int data[12] = { /* ... */ };// 列優先的 3x4 視圖std::mdspan<int, std::extents<3,4>, std::layout_left> col_major(data);
}
4. 切片與子視圖
mdspan
?支持通過切片操作獲取子視圖:
auto sub_view = std::submdspan(mat, 1, std::full_extent); // 獲取第2行所有列
std::cout << sub_view[2]; // 輸出原數組的 mat[1][2]
5. 結合算法使用
多維數組與標準庫算法協同工作:
#include <algorithm>
#include <mdspan>int main()
{int data[3][4] = { /* ... */ };std::mdspan mat(data);// 遍歷所有元素std::for_each(mat.data_handle(), mat.data_handle() + mat.size(), [](int& x) { x *= 2; });
}
5. 布局策略
std::mdspan
?允許您指定用于訪問底層內存的布局策略。默認情況下,使用?std::layout_right
(C、C++ 或 Python 風格),但您也可以指定?std::layout_left
(Fortran?或 MATLAB 風格)。
使用布局策略std::mdspan
和std::layout_right
遍歷兩個std::layout_left
可以看出差異。
#include <mdspan>
#include <iostream>
#include <vector>int main()
{std::vector myVec{1, 2, 3, 4, 5, 6, 7, 8};std::mdspan<int, std::extents<std::size_t, // (1)std::dynamic_extent, std::dynamic_extent>, std::layout_right> m{myVec.data(), 4, 2};std::cout << "m.rank(): " << m.rank() << '\n';for (std::size_t i = 0; i < m.extent(0); ++i) {for (std::size_t j = 0; j < m.extent(1); ++j) {std::cout << m[i, j] << ' '; }std::cout << '\n';}std::cout << '\n';std::mdspan<int, std::extents<std::size_t, // (2)std::dynamic_extent, std::dynamic_extent>, std::layout_left> m2{myVec.data(), 4, 2};std::cout << "m2.rank(): " << m2.rank() << '\n';for (std::size_t i = 0; i < m2.extent(0); ++i) {for (std::size_t j = 0; j < m2.extent(1); ++j) {std::cout << m2[i, j] << ' '; }std::cout << '\n';}}
6. 性能與注意事項
-
連續存儲:
mdspan
?不管理內存,需確保底層數據連續。 -
邊界檢查:默認無越界檢查,可通過自定義策略添加。
-
靈活性:適用于科學計算、圖像處理等需要多維數據的場景。
總結
-
傳統多維數組:適合靜態、編譯時已知維度的場景。
-
std::mdspan
:提供動態維度、靈活布局和高效訪問,是 C++23 處理多維數據的現代方式。