首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

掌握 C++17:结构化绑定与拷贝消除的妙用

编程知识
2024年09月12日 14:13

C++17 特性示例

1. 结构化绑定(Structured Binding)

结构化绑定允许你用一个对象的元素或成员同时实例化多个实体。
结构化绑定允许你在声明变量的同时解构一个复合类型的数据结构(如 结构体,std::tuplestd::pair, 或者 std::array)。这样可以方便地获取多个值,而不需要显式地调用 std::tie() 或者 .get() 方法。

使用结构化绑定,能大大提升代码的可读性:

#include <iostream>
#include <string>
#include <unordered_map>

int main() {

  std::unordered_map<std::string,std::string> mymap;
  mymap.emplace("k1","v1");
  mymap.emplace("k2","v2");
  mymap.emplace("k2","v3");

  for (const auto& elem : mymap)
      std::cout << "old: " << elem.first << " : " << elem.second << std::endl;

  for (const auto& [key,value] : mymap)
      std::cout << " new: " << "key: " << key << ", value: " << value  << std::endl;

  return 0;
}

### 细说结构化绑定
为了理解结构化绑定,必须意识到这里面其实有一个隐藏的匿名对象。结构化绑定时引入的新变量名其实都指向这个匿名对象的成员/元素。
绑定到一个匿名实体
如下初始化的精确行为:
struct MyStruct {
  int i = 0;
  std::string s;
};
MyStruct ms;
auto [u, v] = ms;
等价于我们用 ms初始化了一个新的实体 e,并且让结构化绑定中的 u和 v变成 e的成员的别名,类似于如下定义:
auto e = ms;
aliasname u = e.i;
aliasname v = e.s;
这意味着 u和 v仅仅是 ms的一份本地拷贝的成员的别名。然而,我们没有为 e声明一个名称,因此我们不能直接访问这个匿名对象。注意 u和 v并不是 e.i和 e.s的引用(而是它们的别名)。decltype(u)的结果是成员 i的类型,declytpe(v)的结果是成员 s的类型。因此:
std::cout << u << ' ' << v << '\n';
会打印出 e.i和 e.s(分别是 ms.i和 ms.s的拷贝)。

e的生命周期和结构化绑定的生命周期相同,当结构化绑定离开作用域时 e也会被自动销毁。另外,除非使用了引用,否则修改用于初始化的变量并不会影响结构化绑定引入的变量(反过来也一样)  

### 示例代码
```cpp
#include <iostream>
#include <tuple>
#include <vector>
#include <string>
#include <map>
#include <unordered_map>

struct MyStruct {
      int num {1};
      std::string str {"test"};
};

int main() {

  // 使用结构化绑定从 tuple 中提取值
  std::tuple<int, double, std::string> data = std::make_tuple(1, 3.14, "hello");
  auto [a, b, c] = data;
  std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl;


  //value
  MyStruct my_struc;
  auto [num,str] = my_struc;
  std::cout << "num: " << num << ", str: " << str  << std::endl;
  my_struc.num = 2;
  str = "test2";
  std::cout << "num: " << num << ", str: " << str  << std::endl;


  //ref
  MyStruct my_struc2 {2,"test2"};
  const auto& [num2,str2] = my_struc2;
  std::cout << "num2: " << num2 << ", str2: " << str2  << std::endl;
  my_struc2.num = 4;
  std::cout << "num2: " << num2 << ", str2: " << str2  << std::endl;

  return 0;
}

### 总结
理论上讲,结构化绑定适用于任何有 public数据成员的结构体、C 风格数组和“类似元组 (tuple-like)的对
象”:
• 对于所有非静态数据成员都是 public的结构体和类,你可以把每一个成员绑定到一个新的变量名上。
• 对于原生数组,你可以把数组的每一个元素都绑定到新的变量名上。
• 对于任何类型,你可以使用 tuple-like API 来绑定新的名称,无论这套 API 是如何定义“元素”的。对于一个
类型 type这套 API 需要如下的组件:
– std::tuple_size<type>::value要返回元素的数量。
– std::tuple_element<idx, type>::type 要返回第 idx个元素的类型。
– 一个全局或成员函数 get<idx>()要返回第 idx个元素的值。
标准库类型 std::pair<>、std::tuple<>、std::array<> 就是提供了这些 API 的例子。如果结构体和类提供了 tuple-like API,那么将会使用这些 API 进行绑定,而不是直接绑定数据成员。

## 2. 拷贝消除(Copy Elision)

从技术上讲,C++17引入了一个新的规则:当以值传递或返回一个临时对象的时候必须省略对该临时对象的拷贝。
从效果上讲,我们实际上是传递了一个未实质化的对象 (unmaterialized object)。

自从第一次标准开始,C++就允许在某些情况下省略 (elision)拷贝操作,即使这么做可能会影响程序的运行结果(例如,拷贝构造函数里的一条打印语句可能不会再执行)。当用临时对象初始化一个新对象时就很容易出现这种情况,尤其是当一个函数以值传递或返回临时对象的时候。例如:
```cpp
#include <iostream>
#include <tuple>
#include <vector>
#include <string>
#include <map>
#include <complex>


class MyClass
{
public:
  // 没有拷贝/移动构造函数的定义
  MyClass(const MyClass&) = delete;
  MyClass(MyClass&&) = delete;
};

MyClass bar() {
  return MyClass{};       // 返回临时对象
}

int main() {
  MyClass x = bar();  // 使用返回的临时对象初始化x

  return 0;
}
上面的代码用c++14会报错,c++17已经不报错了

然而,注意其他可选的省略拷贝的场景仍然是可选的,这些场景中仍然需要一个拷贝或者移动构造函数。例如:
MyClass foo()
{
  MyClass obj;
  ...
  return obj; // 仍 然 需 要 拷 贝/移 动 构 造 函 数 的 支 持
}
这里,foo()中有一个具名的变量 obj (当使用它时它是左值 (lvalue))。因此,具名返回值优化 (named returnvalue optimization)(NRVO) 会生效,然而该优化仍然需要拷贝/移动支持。当 obj是形参的时候也会出现这种情况:
MyClass bar(MyClass obj) // 传 递 临 时 变 量 时 会 省 略 拷 贝
{
  ...
  return obj; // 仍 然 需 要 拷 贝/移 动 支 持
}

当传递一个临时变量(也就是纯右值 (prvalue))作为实参时不再需要拷贝/移动,但如果返回这个参数的话仍然需要拷贝/ 移动支持因为返回的对象是具名的。 

### 作用
这个特性的一个显而易见的作用就是减少拷贝会带来更好的性能。尽管很多主流编译器之前就已经进行了这种优化,但现在这一行为有了标准的保证。尽管移动语义能显著的减少拷贝开销,但如果直接不拷贝还是能带来很大的性能提升(例如当对象有很多基本类型成员时移动语义还是要拷贝每个成员)。另外这个特性可以减少输出参数的使用,转而直接返回一个值(前提是这个值直接在返回语句里创建)。另一个作用是可以定义一个总是可以工作的工厂函数,因为现在它甚至可以返回不允许拷贝或移动的对象。
例如,考虑如下泛型工厂函数:
```cpp
#include <iostream>
#include <tuple>
#include <vector>
#include <string>
#include <map>
#include <complex>
#include <utility>
#include <memory>
#include <atomic>


template <typename T, typename... Args>
T create(Args&&... args)
{
  return T{std::forward<Args>(args)...};
}


int main() {
  int i = create<int>(42);
  std::unique_ptr<int> up = create<std::unique_ptr<int>>(new int{42});
  std::atomic<int> ai = create<std::atomic<int>>(42);
  std::cout << "ai: " << ai << std::endl;
  return 0;
}



From:https://www.cnblogs.com/liudw-0215/p/18410226
本文地址: http://www.shuzixingkong.net/article/1948
0评论
提交 加载更多评论
其他文章 浅谈 C# 中的顶级语句
前言 在C# 9版本中引入了一项新特性:顶级语句,这一特性允许在不显式定义 Main 方法的情况下直接编写代码。 传统的写法 namespace&#160;TestStatements{ internal&#160;class&#160;Program { static&#160;void&#160
浅谈 C# 中的顶级语句 浅谈 C# 中的顶级语句
AI实战 | 领克汽车线上营销助手:全面功能展示与效果分析
本篇文章的主要目的是为大家提供实现思路,以及如何更好地开发一个助手,而不仅仅是简单地进行拆解。如果采取拆解的方式,一篇文章可能会长达2万+字,还需要配以数十张图片,这将会非常繁琐。因此,针对拆解的详细内容,我计划单独制作一期视频,以帮助大家更清晰地理解。感谢大家对小雨的关注与支持。
AI实战 | 领克汽车线上营销助手:全面功能展示与效果分析 AI实战 | 领克汽车线上营销助手:全面功能展示与效果分析 AI实战 | 领克汽车线上营销助手:全面功能展示与效果分析
006.MinIO基础使用
图形界面基础使用 bucket bucket创建 图形界面创建bucket。 特性: Versioning 开启版本控制,开启版本控制则允许在同一键下保持同一对象的多个版本。 Object Locking 对象锁定防止对象被删除,需要支持保留和合法持有,只能在创建桶时启用。 Quita 配额限制bu
006.MinIO基础使用 006.MinIO基础使用 006.MinIO基础使用
SPiT:超像素驱动的非规则ViT标记化,实现更真实的图像理解 | ECCV 2024
Vision Transformer(ViT) 架构传统上采用基于网格的方法进行标记化,而不考虑图像的语义内容。论文提出了一种模块化的超像素非规则标记化策略,该策略将标记化和特征提取解耦,与当前将两者视为不可分割整体的方法形成了对比。通过使用在线内容感知标记化以及尺度和形状不变的位置嵌入,与基于图像
SPiT:超像素驱动的非规则ViT标记化,实现更真实的图像理解 | ECCV 2024 SPiT:超像素驱动的非规则ViT标记化,实现更真实的图像理解 | ECCV 2024 SPiT:超像素驱动的非规则ViT标记化,实现更真实的图像理解 | ECCV 2024
.net core8 使用Swagger(附当前源码)
说明 该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发)。 该系统文章,我会尽量说的非常详细,做到不管新手、老手都能看懂。 说明:OverallAuth2.0 是一个简单、易懂、功能强大的权限+可视化流程管理系统。 有兴趣的朋友,请关注我吧(*^▽^*)
.net core8 使用Swagger(附当前源码) .net core8 使用Swagger(附当前源码) .net core8 使用Swagger(附当前源码)
架构师备考的一些思考(三)
前言 这个考题的大部分内容,我感觉都是我们会的,但所有的考题都穿上了马甲,穿上马甲我们就不好认了,而且如果是一个两个人穿马甲,还好推断,如果1000人穿马甲,你识别的概率就会急速下降。 有些题的内容则是即无法识别,也无法背,因为它也没有个前因后果,完全是出题人拍脑袋想的,所以,这种题我们是无法通过知
架构师备考的一些思考(三)
面试官:说说停止线程池的执行流程?
对于我们使用的线程池 ThreadPoolExecutor 来说,停止线程池的方法有以下两个: shutdown():优雅的关闭线程池,即不再接受新任务,但会等待已提交任务(包括正在执行的任务和在队列中等待的任务)执行完毕。等待所有任务都执行完毕后,线程池才会进入终止状态。 shutdownNow(
面试官:说说停止线程池的执行流程? 面试官:说说停止线程池的执行流程? 面试官:说说停止线程池的执行流程?
SpringCloud入门(二)服务间调用和案例
一、微服务拆分注意事项微服务拆分注意事项:1.单一职责:不同微服务,不要重复开发相同业务2.数据独立:不要访问其它微服务的数据库3.面向服务:将自己的业务暴露为接口,供其它微服务调用 1.微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同业务2.微服务可以将业务暴露为接口,供其它微服务使用3
SpringCloud入门(二)服务间调用和案例 SpringCloud入门(二)服务间调用和案例