C++新特性整理目录:

结构化绑定是 C++17 标准引入的一项重要语言特性,它本质上是一颗语法糖,通过提供声明式的数据解包语法,允许开发者将聚合类型(如结构体、std::pair、std::tuple、数组等)的多个数据成员一次性解包并绑定到一组独立的变量标识符上,使得代码更加简洁和可读。

基本语法:
auto [identifier1, identifier2, ... , identifierN] = expression;
auto [identifier1, identifier2, ... , identifierN] { expression };
auto [identifier1, identifier2, ... , identifierN] ( expression );
  • auto: 必须使用 auto 来声明,编译器会自动推导类型。
  • [ ]: 方括号内是你想要绑定的变量名列表,数量必须与表达式返回的成员数量严格匹配。
  • expression: 可以是一个返回 std::pair, std::tuple, 数组,或者拥有公共数据成员的结构体/类的表达式。
基本使用方法
// 直接绑定,该方式以拷贝的方式实现,修改identifier1, identifier2, ... , identifierN的值,无法影响expression
auto [identifier1, identifier2, ... , identifierN] = expression;
>// 引用绑定,该方式以引用的方式实现,修改identifier1, identifier2, ... , identifierN的值,将影响expression
auto [identifier1, identifier2, ... , identifierN] = expression;
>// 常量引用绑定,该方式以常量引用的方式实现,无法identifier1, identifier2, ... , identifierN的值,否则编译不通过
auto [identifier1, identifier2, ... , identifierN] = expression;
1、绑定到数组
// 基本数组绑定
int arr[3] = { 10, 20, 30 };
auto [a, b, c] = arr;
std::cout << "a=" << a << ", b=" << b << ", c=" << c << std::endl;

// 引用绑定修改原数组
auto& [a1, b1, c1] = arr;
a1 = 100;  // 修改 arr[0]
std::cout << "arr[0] = " << arr[0] << std::endl; // 输出 100

// 常量引用绑定(只读)
const auto& [a2, b2, c2] = arr;
//m = 200; // 错误:不能修改常量引用
std::cout << "a2=" << a2 << ", b2=" << b2 << ", c2=" << c2 << std::endl;

输出如下:
在这里插入图片描述

2、绑定到std::pair
 // 拷贝赋值方式
std::pair<int, std::string> student{ 101, "Alice" };
auto [id, name] = student;
std::cout << "Student ID: " << id << ", Name: " << name << std::endl;

// 引用方式
auto& [rid, rname] = student;
rname = rname + ",New";
rid += 1;

 // const 引用
const auto& [cid, cname] = student;
std::cout << "Student ID: " << cid << ", Name: " << cname << std::endl;

输出如下:
在这里插入图片描述

3、绑定到std::tuple
  // 基本绑定
 std::tuple<int, double, std::string, bool> product{ 123, 29.99, "Laptop", true };
 auto [product_id, price, product_name, in_stock] = product;
 std::cout << product_name << " costs $" << price << std::endl;

 // 引用绑定修改原数组
 auto& [rproduct_id, rprice, rproduct_name, rin_stock] = product;
 rprice *= 2.0;

 // 常量引用绑定(只读)
 const auto& [cproduct_id, cprice, cproduct_name, cin_stock] = product;
 std::cout << cproduct_name << " costs $" << cprice << std::endl;

输出如下:
在这里插入图片描述

4、绑定到std::array
 std::array<double, 4> coordinates{ 1.5, 2.5, 3.5, 4.5 };
 auto [x, y, z, w] = coordinates;
 std::cout << "Coordinates: (" << x << ", " << y << ", " << z << ", " << w << ")" << std::endl;

 auto& [rx,ry, rz, rw] = coordinates;
 rw *= 2;

 const auto& [cx, cy, cz, cw] = coordinates;
 std::cout << "Coordinates: (" << cx << ", " << cy << ", " << cz << ", " << cw << ")" << std::endl;

输出如下:
在这里插入图片描述

5、绑定到自定义类/结构体

(1)当类/结构体的所有成员变量都是公有的时,可以直接绑定,如下所示

struct Product {
    int _id;
    std::string _name;
    double _price;
    bool _in_stock;
    Product(int id, std::string name, double price, bool in_stock)
        : _id(id), _name(name), _price(price), _in_stock(in_stock) {
    }
};

class CProduct
{
public:
    int _id;
    std::string _name;
    double _price;
    bool _in_stock;

    CProduct(int id, std::string name, double price, bool in_stock)
        : _id(id), _name(name), _price(price), _in_stock(in_stock) {
    }
};

int main()
{ 
   Product p(1001, "Jon", 35.26, true);
   const auto& [pid, pn, pp, pi] = p;
   std::cout << "Product: (" << pid << ", " << pn << ", " << pp << ", " << pi << ")" << std::endl;

   CProduct cp(1001, "Jon", 35.26, true);
   auto& [cpid1, cpn1, cpp1, cpi1] = cp;
   cpp1 *= cpp1;
   
   const auto& [cpid, cpn, cpp, cpi] = cp;
   std::cout << "CProduct: (" << cpid << ", " << cpn << ", " << cpp << ", " << cpi << ")" << std::endl;  
}

输出如下:
在这里插入图片描述

(2)当类/结构体包含非公有的成员变量时,需要特化std::tuple_size 、std::tuple_element以及get 函数实现,如下所示

class CProduct1
{
private:
   int _id;
   std::string _name;
public:
   CProduct1(int id, std::string name, double price, bool in_stock)
       : _id(id), _name(name), _price(price), _in_stock(in_stock) {
   }

   // 允许结构化绑定的友元函数
   template<size_t Index>
   friend auto get(const CProduct1& item);

   //template<size_t Index>
   //friend const auto& get(const CProduct1& item);

   template<size_t Index>
   friend auto& get(CProduct1& item);
public:
   double _price;
   bool _in_stock;
};
// 特化 std::tuple_size 和 tuple_element
namespace std {

   template<>
   struct tuple_size<CProduct1> {
       static constexpr size_t value = 4;
   };
   template<size_t Index>
   struct tuple_element<Index, CProduct1> {
       using Step1 = remove_const_t<decltype(get<Index>(declval<CProduct1>()))>; 
       using Step2 = remove_volatile_t<Step1>;      
       using type = remove_reference_t<Step2>;     
       //using type = std::remove_cvref_t<decltype(get<Index>(declval<CProduct1>()))>; // C++20提供>了std::remove_cvref_t
   };
}

// 方法1的get函数
template<size_t Index>
auto get(const CProduct1& item) {
   if constexpr (Index == 0) return item._id;
   else if constexpr (Index == 1) return item._name;
   else if constexpr (Index == 2) return item._price;
   else if constexpr (Index == 3) return item._in_stock;
}

//template<size_t Index>
//const auto& get(const CProduct1& item) {
//    if constexpr (Index == 0) return item._id;
//    else if constexpr (Index == 1) return item._name;
//    else if constexpr (Index == 2) return item._price;
//    else if constexpr (Index == 3) return item._in_stock;
//}

template<size_t Index>
auto& get(CProduct1& item) {
   if constexpr (Index == 0) return item._id;
   else if constexpr (Index == 1) return item._name;
   else if constexpr (Index == 2) return item._price;
   else if constexpr (Index == 3) return item._in_stock;
}
int main()
{ 
   CProduct1 cp1(1001, "Jon", 35.26, true);
   auto& [cpid1, cpn1, cpp1, cpi1] = cp1;
   std::cout << "CProduct: (" << cpid1 << ", " << cpn1 << ", " << cpp1 << ", " << cpi1 << ")" << std::endl;
   cpp1 *= 2.0;

   const auto& [cpid2, cpn2, cpp2, cpi2] = cp1;
   std::cout << "CProduct: (" << cpid2 << ", " << cpn2 << ", " << cpp2 << ", " << cpi2 << ")" << std::endl;
}

输出如下:
在这里插入图片描述
从上面的代码中,我们分别实现了如下两个get函数

// 用于读
  template<size_t Index>
  friend auto get(const CProduct1& item);
  
// 用于写
  template<size_t Index>
  friend auto& get(CProduct1& item);

为什么要实现两个呢,如果我们只实现一个会怎么样?
让我们分别来试试吧,我们先注释掉template<size_t Index> friend auto get(const CProduct1& item);相关代码,然后编译试试:
嗯,好吧编译不通过
在这里插入图片描述
为什么呢?我也不清楚,如果我知道原因的朋友,麻烦评论一下呗!
我们再注释掉 template<size_t Index> friend auto& get(CProduct1& item)相关的代码试试,编译运行,结果如下:
在这里插入图片描述
我们的修改没有影响到cp1了,这说明,如果我们想要通过引用方式解包对象并且能够通过解包结果修改对象,那么我们就必须实现

  template<size_t Index>
  friend auto& get(CProduct1& item);

然后

  template<size_t Index>
  friend auto get(const CProduct1& item);

是必须实现的。
大家应该注意到,我注释了这样一段代码:

  template<size_t Index>
  friend const auto& get(const CProduct1& item);

他有什么用呢?准确来说friend const auto& get(const CProduct1& item)可以替换为 friend auto get(const CProduct1& item),并且效率更高,因为少了一次拷贝,难道不是吗?但是我发现,这会导致修改解包得到的变量时,编译不通过。
在这里插入图片描述
即使我实现了friend auto& get(CProduct1& item);。
所以,对于只读的对象,我们建议实现:friend const auto& get(const CProduct1& item);
对于需要读写的对象,没办法,我们必须实现friend auto& get(CProduct1& item)和 friend auto get(const CProduct1& item)

如果希望自定义的类支持结构化绑定,考虑使用公有数据成员,但是这并不能盲目进行哦,需要在在需要严格封装和需要便利性之间找到平衡点。那么我们应该如何抉择呢?这当然取决于我们对类的语言、职能要求了

5、map 遍历(最常用)

示例代码如下:

  std::map<std::string, int> person_map{ {"Alice", 25}, {"Bob", 30}, {"Charlie", 35} };

  for (const auto& [person_name, age] : person_map) {
      std::cout << person_name << " is " << age << " years old" << std::endl;
  }

  for (auto& [person_name, age] : person_map) {
      age++;
  }

  for (const auto& [person_name, age] : person_map) {
      std::cout << person_name << " is " << age << " years old" << std::endl;
  }
对比分析

结构化绑定的作用是什么呢?前面已经说了:解包元组。其本质是语法糖,使得代码更加简洁和可读。下面我将从“解决 C++ 多返回值处理的历史难题”的角度,来进行简单说明。
我们先思考一下,在C++17之前,如何解决一个函数“多返回值处理”的问题?
(1)定义结构体,只返回结构体

struct ReturnType
{
   int result;
   std::string err;
   bool state;
};
ReturnType parseString(const std::string& input)
{
   ReturnType r;
   // 实现具体逻辑
   //r.err = "";
   //r.result = 1;
   //r.state = false;
   return r;
}
int main()
{
	ReturnType result = parseString("test input");
	if (result.state)
	{
  		 // 
	}
}

(2)以引用参数方式

bool parseString(const std::string& input, int& value, std::string& error)
{
	// 实现具体逻辑
}
int main()
{
	int result;
	std::string err;
	if (parseString("123", result, err)) {
   	// ...
	}
}

(2)使用tie解包元组

// 返回 tuple - 代码像在解谜
std::tuple<bool, int, std::string> parseString(const std::string& input)
{
	//return std::make_tuple(true,1,"err info")
}
int main()
{
	// 1、直接使用tuple
	auto result = parseString("123");
	if (std::get<0>(result)) {          // 这到底是什么?
   	int value = std::get<1>(result); // 又是什么?
  		// std::get<2> 是错误信息?记不住!
	}

	// 2、使用tie
	int result;
	std::string err;
	bool state;
	tie(state,result,err) = parseString("123");
	if(state)
	{
		//
	}
}

可以实现,但是是不是都比较复杂或者说麻烦?我们再来看看结构化绑定的实现代码:

// 清晰的函数声明
std::tuple<bool, int, std::string> parseString(const std::string& input)
{
	//return std::make_tuple(true,1,"err info")
}
int main()
{
	// 自文档化的使用方式
	auto [success, value, error_msg] = parseString("123");
	if (success) {
	    std::cout << "Value: " << value << std::endl;
	} else {
	    std::cout << "Error: " << error_msg << std::endl;
	}
}

从这些代码中,我们已经能够非常清晰的感觉到结构化绑定给我们带来的便利了,难道不是吗?

关键细节和注意事项
  • 数量必须匹配:声明的变量数量必须与表达式中的成员/元素数量完全一致。

  • 不能嵌套:不能写成 auto [a, [b, c]] = …。

  • 不能指定类型:必须使用 auto,不能写成 int [a, b] = …。如果需要特定类型,可以使用 auto 配合 std::as_const 或使用 const auto& 等。

  • 适用于 Public 成员:对于结构体,只能绑定到 public 成员。编译器会按照成员声明的顺序进行绑>定。

  • const 和引用限定符:它们作用于被绑定的匿名实体,而不是单个变量。

  • const auto& [a, b]:通过常量引用绑定,a 和 b 都是常量引用(或表现为常量)。

  • auto& [a, b]:通过非常量引用绑定,可以通过 a, b 修改原对象。

  • auto [a, b]:通过值绑定,a 和 b 是原对象成员的副本。

  • 不能有现有变量:结构化绑定是声明,不能用于已经存在的变量。

结构化绑定从某种层面可以看做是对std::tie的增强与扩展。tie的使用详见:https://blog.csdn.net/m0_45074715/article/details/154738574?spm=1011.2415.3001.5331

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐