跳至正文

结构化绑定(C++17, C++20) 学习笔记

  • 技术

结构化绑定

在python中是能够很轻易地实现多个返回值的函数的,例如:

def return_multi_values():
  return 100, 20
x, y = return_multi_values()

但是在C++应该是会用类、结构体或者引用来实现,在C++11之后也引入了std::tuple,也可以用来实现类似的功能:

#include <iostream>
#include <tuple>

std::tuple<int, int> return_multiple_values()
{
  return std::make_tuple(11, 7);
}

int main()
{
  int x = 0, y = 0;
  std::tie(x, y) = return_multiple_values();
  std::cout << "x=" << x << " y=" << y << std::endl;
}

这里定义和获取都很麻烦:

  1. 定义的时候需要显示指定返回类型;
  2. 同时在用std::tie解邦定前需要先声明x, y的变量。
    第一个问题可以通过auto来实现:

    auto return_multiple_values()
    {
    return std::make_tuple(11, 7);
    }

    第二个问题就需要使用C++17引入的新特性——结构化绑定

所谓结构化绑定是指将一个或多个名称绑定到初始化对象中的一个或多个子对象(或者元素)上,相当于给初始化对象的子对象起了别名。这里引用和别名是有区别的
使用结构化绑定的方式是 auto[xx, yy, zz] = xxxx,例如:

#include <iostream>
#include <tuple>

auto return_multiple_values()
{
  return std::make_tuple(11, 7);
}

int main()
{
  auto[x, y] = return_multiple_values();
  std::cout << "x=" << x << " y=" << y << std::endl;
}

但右边的值也不一定非得是函数返回值或者tuple,合理的表达式也行:

#include <iostream>
#include <string>

struct BindTest {
  int a = 42;
  std::string b = "hello structured binding";
};

int main()
{
  BindTest bt;
  auto[x, y] = bt;
  std::cout << "x=" << x << " y=" << y << std::endl;
}

在for循环里会有更好的效果:

#include <iostream>
#include <string>
#include <vector>

struct BindTest {
  int a = 42;
  std::string b = "hello structured binding";
};

int main()
{
  std::vector<BindTest> bt{ {11, "hello"},  {7, "c++"},  {42, "world"} };
  for (const auto& [x, y] : bt) {
       std::cout << "x=" << x << " y=" << y << std::endl;
  }
}

深入理解

这两个理解是错误的:

  1. 结构化绑定的目标就是等号右边的对象
  2. 所谓的别名就是对等号右边对象的子对象或者元素的引用。

在结构化绑定中编译器会根据限定符生成一个等号右边对象的匿名副本,而绑定的对象正是这个副本而非原对象本身
auto [xx, yy, zz]是能够增加修饰符的,例如const,volatile等, 甚至&引用也可以,这些都会直接作用在新的变量上。

结构化绑定3种类型

包括原生数组、结构体和类对象、元组和类元组的对象。

绑定原生数组

它是所有情况中最简单的,条件是别名数量和数组数量一致。

绑定到结构体和类对象

首先,类或者结构体中的非静态数据成员个数必须和标识符列表中的别名的个数相同;其次,这些数据成员必须是公有的(C++20标准修改了此项规则,详情见20.5节);这些数据成员必须是在同一个类或者基类中;最后,绑定的类和结构体中不能存在匿名联合体

绑定到元祖和类元祖对象

类元祖对象的定义比较复杂,不像python有ABC这种元类,C++是通过模板实现判断,目标类型提供std::tuple_size、std::tuple_element以及get的特化或者偏特化版本即算是类元祖对象。

在标准库中,除了std::tuple,还有std::pair和std::array也满足这个条件,这就带来了一个很好的特性:能够在for循环遍历map的时候直接使用结构化绑定,例如:

#include <iostream>
#include <string>
#include <map>

int main()
{
  std::map<int, std::string> id2str{ {1, "hello"}, {3, "Structured"}, {5, "bindings"} };

  for (const auto& elem : id2str) {
       std::cout << "id=" << elem.first
            << ", str=" << elem.second << std::endl;
  }
}

End.

标签:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

目录