C++17特性:结构化绑定(三)应用场景

论坛 期权论坛 期权     
软件开发谈   2019-7-28 23:29   2566   0
从原理上讲,结构化绑定可用于拥有public成员变量的结构体,原始的C风格数组,以及类Tuple的对象:
  • 对于所有非静态(static)的成员变量都是public的结构体和类,可以将每个非静态成员变量绑定到一个确切的名字上。
  • 对于原始数组,可以将数组的每个元素绑定到一个确切的名字上。
  • 对于任意类型,可以使用类Tuple的API来绑定名字,API定义其中的元素。API大体上需要为类型type定义如下组件:
    • std::tuple_size::value要返回元素个数。
    • std::tuple_element::type返回第idx个元素的类型。
    • 全局或成员函数get()返回第idx个元素的值。
标准库中的类型std::pair, std::tuple, and std::array就是典型的提供了这些API的例子。
如果结构体或类提供了类Tuple的API,这些API就会被使用。
所有应用场景中,元素或成员变量个数要跟声明的结构化绑定的名字个数一致。不能跳过名字,也不能一个名字使用2次。然而,可以使用非常短的名字,比如‘_’(有些程序员喜欢,有些不喜欢并且不允许在全局名字空间内使用),但是这在同一个可见域中只能用一次:

  1. auto [_,val1] = getStruct(); // OK
复制代码
  1. auto [_,val2] = getStruct(); // 错误: 名字 _ 已经用过
复制代码
同时,也不支持嵌套。
下面将仔细介绍一下这些结构化绑定的用法。

  • 结构体和类
前面的示例中已经有过简单的描述了拥有public成员变量的结构体和类如何使用结构化绑定。典型的应用是在一个数据结构中直接使用多个返回值。但是要注意一些边界情况。
注意,继承的时候可能有使用限制。所有非静态成员变量必须是同一个类定义的成员(因此,它们必须是某个类型的成员,或同一个无歧义的public基类的成员):
  1. struct B {
复制代码
  1. int a = 1;
复制代码
  1. int b = 2;
复制代码
  1. };
复制代码
  1. struct D1 : B {
复制代码
  1. };
复制代码
  1. auto [x, y] = D1{}; // OK
复制代码
  1. [/code][code]struct D2 : B {
复制代码
  1. int c = 3;
复制代码
  1. };
复制代码
  1. auto [i, j, k] = D2{}; // 编译错误
复制代码
要注意的是,只有public成员变量的顺序是稳定的才能使用结构化绑定。否则,如果B里的int aint b顺序变了,xy就得到不同的值了。为了支持这种稳定性,C++17为一些标准库中的结构体定义了成员的顺序(以后也许会讲到)。
联合体(union)不允许结构化绑定。

  • 原始数组
下面的代码以原始C风格数组的2个元素初始化了xy
  1. int arr[] = { 47, 11 };
复制代码
  1. auto [x, y] = arr; // x 和 y 是数组arr的一份拷贝的int元素
复制代码
  1. auto

    C++17特性:结构化绑定(三)应用场景

    从原理上讲,结构化绑定可用于拥有public成员变量的结构体,原始的C风格数组,以及类Tuple的对象:
    对于所有非静态(static)的成员变量都是public的结构体和类,可以将每个非静态成员变量绑定到一个确切的名字上。
    对于原始数组,可以将数组的每个元素绑定到一个确切的名字上。
    对于任意类型,可以使用类Tuple的API来绑定名 ...查看全文
    软件开发谈 发表于 2019-7-28 23:29 
= arr; // 错误: 元素个数不一致
复制代码只有当数组大小已知的情况下才能用。如果数组作为参数传递,就不能用了,因为它的类型会衰减为指针。
C++允许返回已知大小的数组的引用,所以这个特性可用于函数返回一个数组,且知道其大小:
  1. auto getArr() -> int(&)[2]; // getArr() 返回原始int 数组的引用
复制代码
  1. ...
复制代码
  1. auto [x, y] = getArr(); // x 和y 是返回的数组arr的一份拷贝中的int元素
复制代码
也可以将结构化绑定用于std::array,它提供了类Tuple的API,后面会介绍。

  • std::pair, std::tuple以及std::array
结构化绑定机制是可扩展的,因此可以给任意类型增加结构化绑定支持。标准库就将此特性应用到std::pairstd::tuplestd::array
std::array
例如,下面的代码初始化了abcd作为getArray()返回的std::array的一份拷贝的4个元素的名字:
  1. std::array getArray();
复制代码
  1. ...
复制代码
  1. auto [a,b,c,d] = getArray(); // a,b,c,d是返回值的另一份拷贝的4个元素的名字
复制代码
这里abcdgetArray()返回的std::array的一份拷贝的元素的结构化绑定。
写入操作也是支持的,只需将非临时返回值作为初始化的值即可。例如:
  1. std::array stdarr { 1, 2, 3, 4 };
复制代码
  1. ...
复制代码
  1. auto& [a,b,c,d] = stdarr;
复制代码
  1. i += 10; // 修改stdarr[0]
复制代码
std::tuple
下面的代码初始化了abc作为getTuple()返回的std::tuple的一份拷贝的3个元素的别名:
  1. std::tuple getTuple();
复制代码
  1. ...
复制代码
  1. auto [a,b,c] = getTuple(); // a,b,c的类型和值是返回的tuple的3个元素的类型和值
复制代码
也就是说,a的类型是charb的类型是floatc的类型是std::string

std::pair
作为另一个例子,在关联容器或无序容器上
  1. std::map coll;
复制代码
调用insert()函数,处理其返回值的代码,可以通过避免使用更泛化的名字first和second使得可读性更高:
  1. auto ret = coll.insert({"new",42});
复制代码
  1. if (!ret.second){
复制代码
  1.     // 如果插入失败,则使用迭代器 ret.first 处理错误
复制代码
  1.     ...
复制代码
  1. }
复制代码
绑定名字可以在语义上更好地表达它们的目的:
  1. auto [pos,ok] = coll.insert({"new",42});
复制代码
  1. if (!ok) {
复制代码
  1.     // 如果插入失败,则使用迭代器 pos处理错误:
复制代码
  1.     ...
复制代码
  1. }
复制代码
要注意到的是,这个特殊的例子在C++17中提供了“带初始化的if”来更好地解决。

给pair和tuple的结构化绑定赋新值
声明结构化绑定后,通常不能再修改所有绑定,因为结构化绑定只能声明而不能一起使用。然而,如果已经赋值了,可以使用std::tie()std::pairstd::tuple的值进行再赋值。
实现如下:
  1. std::tuple getTuple();
复制代码
  1. ...
复制代码
  1. auto [a,b,c] = getTuple(); // a,b,c是返回的 tuple的类型和值 ..
复制代码
  1. std::tie(a,b,c) = getTuple(); // a,b,c得到下一个返回的tuple的值
复制代码
这就特别适合用于实现循环调用和处理返回值是pair的情况,比如在循环中进行搜索:
  1. std::boyer_moore_searcher bmsearch{sub.begin(), sub.end()};
复制代码
  1. for (auto [beg, end] = bmsearch(text.begin(), text.end());
复制代码
  1.      beg != text.end();
复制代码
  1.      std::tie(beg,end) = bmsearch(end, text.end())) {
复制代码
  1.     ...
复制代码
  1. }
复制代码
请继续关注本公众号下一篇《C++17特性:结构化绑定(四)自定义API支持结构化绑定》。

长按二维码关注公众号“软件开发谈”
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:10
帖子:2
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP