std::span 是 C++20 加入的一個新的類別(文件),他的基本概念是「對連續資料做處理的一個觀察者(view)」;由於只是「觀察者」,所以他並不具有所有權,某種意義上,可以看成類似一組連續資料的局部參考。
在最簡單的用法上,個人覺得它可以用來處理傳統陣列的傳遞;某種意義上算是做個封包、包裝成 STL 的容器的形式。下面是一個簡單的範例:
#include <iostream> #include <span> #include <algorithm> int main() { int aData[]{ 5, 4, 3, 2, 1 }; std::span sData(aData); std::cout << sData.size() << "\n"; // 5 std::ranges::sort(sData); }
在上面的例子可以看到,這邊是透過 sData 來重新封包傳統陣列 aData,之後就可以按照使用 C++ 容器的習慣來操作了~同時,這也讓他更方便地利用 C++ 提供的演算法(這邊是拿 sort() 來舉例)。
而實際上,他也可以只用來對應局部的資料(但是還是要連續),例如:
std::span sData(aData + 1, aData + 4); std::cout << sData.size() << "\n"; // 3 std::ranges::sort(sData); for (int i = 0; i < 5; ++i) std::cout << aData[i] << ",";
這邊 sData 就僅會對應到 aData 的第 2 – 4 的資料;也因此在對 sData 進行排序後,再把整個 aData 輸出的話,結果會變成是:
5,2,3,4,1,
另外,std::span 也有提供 subspan() 這個函式,可以更直覺地產生僅對應部分區域的 span 物件;像在下面的程式中,sData1 和 sData2 就是對應到相同的資料。
std::span sData1(aData + 1, aData + 4); auto sData2 = std::span(aData).subspan(1, 3);
以個人來說,個人會覺得 subspan() 的寫法比較直覺;而除了 subspan() 外,他也還有 first() 和 last() 可以使用。
而除了針對傳統的陣列進行操作外,std::span 也理所當然地可以用 STL 的連續型容器上;以 std::vector 來說,就是下面的形式:
std::vector<int> vData{ 5, 4, 3, 2, 1 }; auto sData = std::span(vData).subspan(1, 3);
如果有必要僅針對容器的部分範圍操作的話,在使用上也算相當方便。
而在 Heresy 來看,std::span 最容易實用的地方,應該是拿來當作函式的引數來使用。
在傳統的 C++ 裡面,如果要傳遞一個陣列的話,還需要同時傳遞他的大小,才能進行操作;下面就是一個簡單的例子:
void output(const int aData[], size_t uSize) { std::cout << "Size: " << uSize << "\n"; for (size_t i = 0; i < uSize; ++ i) std::cout << aData[i] << "\n"; } int main() { int aData[]{ 5, 4, 3, 2, 1 }; output(aData, 5); }
如果一個沒弄好,陣列大小忘了修改的話,就可能會出現大問題。
而如果透過 std::span 的話,則可以改寫成:
void output(const std::span<int>& aData) { std::cout << "Size: " << aData.size() << "\n"; for (int& d : aData) std::cout << d << "\n"; }
如此一來,output() 這個函式就可以同時適用於傳統的陣列以及符合轉換條件的容器了!
int aData[]{ 5, 4, 3, 2, 1 }; output(aData); std::vector<int> vData{ 5, 4, 3, 2, 1 }; output(vData); output(std::span(vData).last(3));
而如果僅要針對部分資料處理,也可以透過 subspan() 等函式簡單地做到,在個人來看,對特定狀況下的程式開發,算是可以做到相當地簡化的!
而且,透過 std::span 除了可以很方便地用同樣的介面操作局部的資料外,也不會有額外複製資料的成本,算是滿實用的。
如果編譯器還不支援 C++20 的話,目前在 GSL 和 Boost(boost::span、文件)裡、也都有提供同樣的東西可以使用。
而除了 std::span 外,在 C++17 的時候也有提供 std::string_view(文件);他和 std::span 有點像,應該可以視為針對字串特化的唯讀版本(參考《Differences between std::string_view and std::span》)。
如果輸入的字串不需要修改的話,那把函式的引數改成用 std::string_view,也可以增加使用上的彈性。
#include <iostream> #include <string_view> void output(const std::string_view sText) { std::cout << sText << "\n"; } int main() { output("Hello world"); const char* sText1 = "Hello world"; output(sText1); std::string sText2 = "Hello world"; output(sText2); }
同時,由於 std::string_view 不需要做資料的複製,所以如果是要取部分字串出來做比較的話,也會比使用 substr() 來的有效率許多~