结构化绑定
结构化绑定允许你用一个对象的元素或成员同时实例化多个实体。
概念
1 | struct Test |
这里变量intVal
和strVal
的声明方式成为结构化绑定。某种程度上可以说它们分解了Decompose
用来初始化的对象(有些观点称它们为分解声明Decomposing Declaration
)。
支持的使用方式
1 | auto [intVal, strVal] = test; |
应用
在基于范围的for循环中的使用
std::map中的应用
通常,在C++11
遍历std::map
时,使用如下方式:
1 | std::map<int, std::string> dataMap; |
此处的e
的类型为std::pair<int, std::string>
。该类型的两个成员分别是first
与second
。
但如果使用结构化绑定,可以写成如下形式:
1 | for(const auto& [key, value] : dataMap) |
std::tuple与struct的应用
当然,std::tuple
也可以不再使用std::get
与index
或type
来获取:
1 | std::list<std::tuple<int, char, std::string>> dataList; |
struct
亦是如此:
1 | struct Test |
能够大大提高代码可读性。
在带初始化的if和switch语句中的使用
有关带初始化的if和switch语句可点击链接。此处只讲解结构化绑定在此处的应用。
带初始化的if
此处的例子为向std::map
或者std::unorderedmap
插入元素。可以像下面这样检查是否成功:
1 | std::map<std::string, int> dataMap; |
这里,我们用了结构化绑定给返回值的成员和pos
指向的值的成员声明了新的名称, 而不是直接使用first
和second
成员。
带初始化的switch语句
通过使用带初始化的switch
语句,我们可以在对条件表达式求值之前初始化一个对象/实体。
例如,我们可以先声明一个文件系统路径,然后再根据它的类别进行处理:
1 | namespace fs = std::filesystem; |
详解结构化绑定
原理
为了理解结构化绑定,必须意识到这里面其实有一个隐藏的匿名对象。
结构化绑定时引入的新变量名其实都指向这个匿名对象的成员/元素。
下面这个代码:
1 | auto [intVal, strVal] = test; |
其实等价于:
1 | auto e = test; |
这意味着intVal
和strVal
仅仅是test
的一份本地拷贝的成员的别名。 然而,我们没有为e
声明一个名称,因此我们不能直接访问这个匿名对象。
1 | std::cout << u << " " << v << std::endl; |
会打印出e.i
以及e.str
的值(分别是test.i
与test.str
的拷贝)。
e
的生命周期和结构化绑定的生命周期相同,当结构化绑定离开作用域时e
也会被自动销毁。另外,除非使用了引用,否则修改用于初始化的变量并不会影响结构化绑定引入的变量(反过来也一样)。intVal
和strVal
都不是引用,只有匿名实体e
是一个引用。
修饰符
&
上述情况中,如果想要使结构化绑定引入的变量影响源变量,则需要使用&
:
1 | Test test{1, "std"}; |
const
一个结构化绑定声明为const引用:
1 | const auto &[intVal, strVal] = test; |
这里,匿名实体被声明为const
引用, 而intVal
和strVal
分别是这个引用的成员i
和str
的别名。 因此,对test的成员的修改会影响到u和v的值:
1 | test.i = 2; |
如果一个结构化绑定是引用类型,而且是对一个临时对象的引用,那么和往常一样, 临时对象的生命周期会被延长到结构化绑定的生命周期:
1 | Test GetStruct() |
移动语义
如下声明:
1 | Test test = {1, "str"}; |
这里intVal
和strVal
指向的匿名实体是test
的右值引用, 同时test
仍持有值。
然而,你可以对指向test.str
的strVal
进行移动赋值:
1 | std::string s = std::move(strVal); // 把test.str移动到s |
像通常一样,值被移动走的对象处于一个值未定义但却有效的状态。因此可以打印它们的值, 但不要对打印出的值做任何假设。
注意
- 在任何情况下,结构化绑定中声明的变量名的数量都必须和元素或数据成员的数量相同。 你不能跳过某个元素,也不能重复使用变量名。然而,你可以使用非常短的名称例如’_‘ (有的程序员喜欢这个名字,有的讨厌它,但注意全局命名空间不允许使用它),但这个名字在同一个作用域只能使用一次:
1 | auto [_, val1] = getStruct(); // OK |
- 目前还不支持嵌套化的结构化绑定。