在 C++ 裡頭有相當多「容器」。從原生的陣列,到標準庫 STL 的 vector, array, list, queue, map, set, …。有時候我們只是想以檢視的角度去看一個容器,或是其中一段內容,而不需要底下龐大的資料結構支撐其運作,也不想要擁有這個容器內的元素,這就是 C++20 中標準庫引入 span 概念的原由。

同樣概念在 C++17 時代就針對 string 這個「容器」有相對應的解決方案,就是 string_view字串視圖。

對,他就是個不具資料擁有權的代理 (proxy) 而已。我們今天就來帶各位同學解析一下 span 的用法還有原理。

長怎樣 / 怎麼用

根據 span標準提案 (P0122R7) 裡的敘述,

std::span 是一個不具所有權且用來檢視連續資料的一個觀察者 (view)。

他就藏在標頭檔 <span> 裡面。各位同學不是隔壁棚

的親戚。

#include <span>

而他的定義在標準中大概 / 可能 / 可以長這樣子:

template<
class T,
std::size_t Extent = std::dynamic_extent
> class span;

意思就是他其實是一個樣版類別 (template class)。依照我們初始化他的容器內容物型別 (可能還有長度) 去推導。

  • T 就是你想檢視的容器內容物的型別,比如你今天想檢視的容器是個 vector<int>,那這兒的 T 就是 int
  • Extent 是非型別樣版參數,代表我們想檢視的容器範圍寬度,可以是一個簡單的非負整數或是 std::dynamic_extent (預設),代表動態寬度。大家看到這裡掛了一個預設引數,就知道在標準庫設計中,span 大部份使用情境之下我們是不太需要在意容器檢視範圍的寬度的。
  • 當然這裡的「動態」當然不是真的可以變長變短,而是指在運行期 (run-time) 初始化 span 時才會知道範圍寬度,編譯期 (compile-time) 還不知道的意思。

包裝 C++ 原生陣列

包裝原生陣列的方法很簡單,就直接把一個原生陣列丟進去 span 的 constructor 創一個 span 物件就好 (C++17 以後,就算是樣版類別,宣告物件時也不用手動丟參數具現化樣版)。如同下面代碼中 main() 裡面的 arr_sp

用 span 包裝 C++ 原生陣列的幾種方法

void print_sp(const std::span<int> &sp)
{
std::cout << "Size: " << sp.size() << std::endl;
for (auto & i : sp)
std::cout << i << ' ';
std::cout << std::endl;
} int main()
{
int arr[] = {0,1,2,3,4,5,6,7,8};
std::span arr_sp{arr};
std::span arr_sp2{arr + 2, arr + 6};
std::span arr_sp3{arr + 3, 4}; std::cout <<arr_sp[2] << std::endl; // 2 print_sp(arr_sp);
// Size: 9
// 0 1 2 3 4 5 6 7 8
print_sp(arr_sp2);
// Size: 4
// 2 3 4 5
print_sp(arr_sp3);
// Size: 4
// 3 4 5 6
}

除了直接包裝整個陣列 (arr_sp) 以外,我們也可以用錨點的方式構造一個 span

  • span 的 constructor 給定陣列的起點還有終點的指標。和標準庫的慣例一樣,作用範圍包含起點、不包含終點。代碼中的 arr_sp2
  • span 的 constructor 給定陣列的起點還有長度。代碼中的 arr_sp3

std::span 這個代理人包裝原生陣列之後,不用說,我們依舊可以像古時候一樣以 [] 去存取底下的陣列元素。

比較炫炮的是,如同print_sp()裡的用法:我們可以現代化的 ranged-base for loop 去列舉代理範圍內的元素 ,呼叫 size()獲得 span 代理的範圍長度,也提供 begin() / end() 這對活寶,也就是通過 C++ 中的 iterator 來存取範圍內元素。

特別要注意的是,[] 如果存取越界的話,這個在標準中說是未定義行為,各家編譯器帶的標準庫或是 open source 實作可以各自表述,所以大家還是要小心點不要亂搞 XD。

古時候把陣列傳來傳去總是需要在函式參數多標示個長度,甚至需要描述區段時,還需要多兩個參數標示起點終點,通過 span 樣版把長度吃進去類別裡面,完全省去這層困擾,且幾近零時間成本,程式也變得簡潔好懂多了。

// Before
int read_info_buf(char buf[], int size) {} char buf[BUF_SIZE];
read_info_buf(buf, BUF_SIZE); // After
int read_info_buf(std::span<char> s) {}
char buf[BUF_SIZE];
read_info_buf(buf);

包裝 C++ 標準庫 STL 的容器 (Container)

身為 C++20 的新發明,span 肯定可以拿來包 C++ 標準庫裡面那堆容器 (Container) 了是吧?是,也不是。

記得我們前面提到 span 代表的是一連串連續的資料。因此能被 span 包裝的容器自然也就必須是連續的容器 (sequential contiguous container):也就是只有 arrayvector(除開 vector<bool>),stringspan

補充一下,舉例來說,在 clang 9.0.1/LLVM 的 span constructor SFINAE 實作方法是判斷

  • std::data(Container) 是否是 well-formed。
  • 而且 std::data(Container) 這個函式回傳的指標所指向的型別形成的陣列能否轉型成 span 想包裝的內容物的陣列。

上面看不懂沒關係,重點是怎麼用,其實跟上面包裝原生的 C++ 陣列 87% 像:直接丟容器給 span。不多說直接上圖

我們從上面代碼的 main() 裡面總結一下 span 幾種包裝 STL 的方法:

  • 直接以一個 array 初始化 span。
  • arraybegin()arrayend() 初始化。
  • a_sp 呼叫 subspan(2, 2) 得到一個從 2 開始、長度為 2 的 span,並拿來初始化一個新的 span
  • 以一個 vector (除開 vector<bool>) 初始化 span
  • 以一個 string 初始化 span

另外也稍微提一下 span 提供的幾個分段函式:

  • first(3): 對一個 span 取前 3 個元素成為一個新的 span
  • last(5): 對一個 span 取後 5 個元素成為一個新的 span
  • subspan(2, 2): 對一個 span 從位置 2 開始,取兩個元素成為一個新的 span

靜態長度 Static extent

前面稍為提到惹,所謂靜態的長度是指在編譯時期就直接知道想包裝的範圍寬度多長。像是前面舉例的代碼裡頭,span 拿來包裝 C++ 原生陣列,或是包裝 **std::array** 在 constructor 都會推導出 static extent,在這裡我自己把他取名叫靜態 **span**

對我們包裝一個容器來說,span 擁有 static extent 帶給我們最大的好處就是可以把代碼寫得更加炫炮。

結合 C++17 Structured Binding

在提案 p1024r3 裡面提到了:既然原生陣列和標準庫的 array 從 C++17 起就支援了 structured binding,那麼能包裝這些東西的 span 也應該要能支援 structured binding,才能達到完整的 array reference 的效用。所以這個提案改動了

  • 實作 std::get 對靜態 span 的支援。
  • 實作 std::tuple_sizestd::tuple_elementspan 的支援。
  • 也就是可以通過 structured binding 直接解構一個靜態 **span**

按照 C++17 的 structured binding 語法完美套用,無縫接軌。直接上圖

編譯器支援 / Open Source

上面所有代碼都在 MacOS 10.15.2 使用 clang/LLVM 9.0.1 編譯測試過,這篇文章寫作當下幾家編譯器大廠也就 clang 的 libc++ 有釋出 std::span 的實作。GCC 說 libstdc++ 10 版會出,而 MSVC 就…再看看 XD

其實 span 的呼聲一直都有,最早可以追溯到 2012 年的 array reference,只是進標準的進程一直很緩慢就是了,所以網路上也產生了各家實作的開源 span 版本,大家可以不用裝 clang 就簡單上手一下,

值得一提的是,隨著 span 的標準進展,除了各種開源實作,另一種 span 的擴展的呼聲和其開源也逐漸冒了出頭,那就是把包裝的連續資料視為多維空間的 mdspan。有興趣的同學也可以去看看 XD 大概是醬 888

C++20 | std::span 陣列、容器的代理人的更多相关文章

  1. std::vector<Channel2*> m_allChannels;容器,以及如何根据channelid的意义

    std::vector<Channel2*> m_allChannels;容器,以及如何根据channelid的意义 这个容器保存了所有客户端连接的channel Channel2* Li ...

  2. [JS奇怪的世界]No.55 危險小叮嚀:陣列與for in

    前言 前面已經瞭解了使用內建函數建構子的某些危險地方,但其實陣列與for in,也是有一些危險的地方. 陣列與for in 在前面幾個章節有講過陣列就是物件,所以我們一樣可以使用 for in來做處理 ...

  3. 使用std::find_if提取序列容器的子串

    一个需求是这样的,一个vector容器中,我需要提取满足一定条件的元素的序列.就比如,一个树形结构,我把该接口拍扁成vector容器,每个节点都有一个惟一ID. 以下就是根据特定的ID查找节点下的子节 ...

  4. PAT Basic 1043 输出PATest (20分)[Hash散列]

    题目 给定⼀个⻓度不超过10000的.仅由英⽂字⺟构成的字符串.请将字符重新调整顺序,按"PATestPATest-."这样的顺序输出,并忽略其它字符.当然,六种字符的个数不⼀定是 ...

  5. Ruby学习笔记0708

    #!/usr/bin/env ruby class MegaGreeter attr_accessor :names # 初始化這個物件 def initialize(names = "Wo ...

  6. 【表格设置】HTML中合并单元格,对列组合应用样式,适应各浏览器的内容换行

    1.常用表格标签 普通    <table>           |           <tr>          |           |          <th ...

  7. 2.9 C++STL map/multimap容器详解

    文章目录 2.9.1 引入 2.9.2 代码示例 map案列 multimap案列 2.9.3 代码运行结果 总结 2.9.1 引入 map相对于set区别,map具有键值和实值,所有元素根据键值自动 ...

  8. unordered容器

    1.散列容器(hash container)  散列容器通常比二叉树的存储方式可以提供更高的访问效率. #include <boost/unordered_set.hpp> #includ ...

  9. c++ 标准库的各种容器(vector,deque,map,set,unordered_map,unordered_set,list)的性能考虑

    转自:http://blog.csdn.net/truexf/article/details/17303263 一.vector vector采用一段连续的内存来存储其元素,向vector添加元素的时 ...

  10. EasyUI 格式化DataGrid列

    easyui DataGrid中格式化列,如果单价低于20,则使用定义列formatter为红色文本.格式化DataGrid列,我们应该设置formatter属性,这个属性是一个函数.格式化函数包括两 ...

随机推荐

  1. Android 11 后的应用数据和文件

    Android应用数据的保存方式有四种,分别是应用专属存储空间.共享存储.偏好设置.数据库. 应用专属存储空间 应用专属存储空间:存放应用专属文件,主要包括两个空间,卸载后移除 内部存储空间:位于系统 ...

  2. Net 高级调试之十:轻量级代码生成的调试

    一.简介 今天是<Net 高级调试>的第十篇文章.说起来,高级调试,调试的内容还是挺多的,技巧也不少,但是,要想做一个合格的高级调试人员,还需要掌握如何调试动态生成的IL代码.今天要探讨的 ...

  3. 2021-09 .NET 5.0.10 Update for x64 Client (KB5006192) 安装失败,错误代码:0x80070643

    上周五日常检查系统更新(强迫症晚期) 出现一项更新:2021-09 .NET 5.0.10 Update for x64 Client (KB5006192) details: https://www ...

  4. 记一次 RestTemplate 请求失败问题的排查 → RestTemplate 默认会对特殊字符进行转义

    开心一刻 今天中午,侄子在沙发上玩手机,他妹妹屁颠屁颠的跑到他面前 小侄女:哥哥,给我一块钱 侄子:叫妈给你 小侄女朝着侄子,毫不犹豫的叫到:妈! 侄子:不是,叫妈妈给你 小侄女继续朝他叫到:妈妈 侄 ...

  5. 用友vs金蝶产品分析(云星空与YonSuite)

    产品定位 用友与金蝶二者面对的客户群体是相同的:都是为成长型企业提供一体化服务,由于金蝶云星空发展较早,在部分产品功能上具备一定的先发优势:在产品的架构上,由于YS采用目前最先进的云原生和微服务架构, ...

  6. word的实用操作技巧

    1.基本使用操作 (1)文本删除: 退格键backspace:删除光标以左的内容 删除键delete:删除光标以右的内容 直接输入会增加字符,按insert键,会切换成改写模式,新字符代替旧字符,总字 ...

  7. 什么是RPC协议

    工作的时候,第一次接触CRPC协议,当时就很懵,啥是CRPC协议,一脸懵逼,于是就到网上去搜,填充知识空缺. 不少解释显得非常官方,我相信大家在各种平台上也都看到过,解释了又好像没解释,都在用一个我们 ...

  8. MySQL运维4-Mycat入门

    一.mycat概述 mycat是阿里巴巴企业下的开源的,基于JAVA语言编写的MySQL数据库中间件,可以像使用MySQL一样来使用Mycat,对于开发人员来说根本感觉不到mycat的存在.之前在国内 ...

  9. Spring整合Quartz简单入门

    创建一个Web项目 导入相关jar包 <?xml version="1.0" encoding="UTF-8"?> <project xmln ...

  10. MinIO客户端之ping

    MinIO提供了一个命令行程序mc用于协助用户完成日常的维护.管理类工作. 官方资料 mc ping 检查指定的MinIO节点的服务是否可用. 不带参数,命令如下: ./mc ping local1 ...