STL pair源码分析

STL pair源码分析

pair是STL中提供的一个简单的struct,用来处理类型不同的一对值,是非常常用的数据结构。这一对值是以public的形式暴露出来的,直接通过first和second就能访问。我们以MSVC提供的STL源码为例,分析pair的具体实现。在分析过程中可能会对源码进行一定程度地简化。

首先是它存储的数据成员,pair中还记录了这一对值的类型:

template <class _Ty1, class _Ty2>
struct pair { // store a pair of valuesusing first_type  = _Ty1;using second_type = _Ty2;_Ty1 first; // the first stored value_Ty2 second; // the second stored value
};

pair的构造函数提供了以下若干种,实现都比较简单,这里整理出来如下:

template <class _Ty1, class _Ty2>
struct pair {pair() : first(), second() {}pair(const _Ty1& _Val1, const _Ty2& _Val2) : first(_Val1), second(_Val2) {}template <class _Other1, class _Other2>pair(_Other1&& _Val1, _Other2&& _Val2) : first(_STD forward<_Other1>(_Val1)), second(_STD forward<_Other2>(_Val2)) {}pair(const pair&) = default;pair(pair&&)      = default;template <class _Other1, class _Other2>pair(const pair<_Other1, _Other2>& _Right) : first(_Right.first), second(_Right.second) {}template <class _Other1, class _Other2>pair(pair<_Other1, _Other2>&& _Right) : first(_STD forward<_Other1>(_Right.first)), second(_STD forward<_Other2>(_Right.second)) {}template <class _Tuple1, class _Tuple2, size_t... _Indices1, size_t... _Indices2>constexpr pair(_Tuple1& _Val1, _Tuple2& _Val2, index_sequence<_Indices1...>, index_sequence<_Indices2...>): first(_Tuple_get<_Indices1>(_STD move(_Val1))...), second(_Tuple_get<_Indices2>(_STD move(_Val2))...) {}template <class... _Types1, class... _Types2>pair(piecewise_construct_t, tuple<_Types1...> _Val1, tuple<_Types2...> _Val2): pair(_Val1, _Val2, index_sequence_for<_Types1...>{}, index_sequence_for<_Types2...>{}) {}
};

通过观察可以得出,pair的构造函数主要分为三类,第一类是传两个值val1,val2进来,pair根据这两个值分别初始化first和second,当传入参数是右值引用时,构造函数需要通过std::forward进行完美转发,保证调用的是first和second的右值引用构造函数;第二类是传另外一个pair对象right进来,构造函数会把right的first和second依次取出,再分别构造自身的first和second,同样这里也需要考虑右值的问题;第三类比较复杂,传入的参数是tuple,tuple类型是C++的多元组,可以包含两个以上的元素。那么这里index_sequencepiecewise_construct_t类型又是什么呢?话不多说,我们来看两个例子,通过例子就能明白了:

#include <iostream>
#include <utility>
#include <tuple>using namespace std;struct A
{int x;int y;int z;A() = default;A(int x, int y, int z) : x(x), y(y), z(z) {}
};int main()
{tuple<int, int, int> t1(1, 2, 3);tuple<int, int, int> t2(6, 5, 4);pair<A, A> p1(t1, t2, index_sequence<0, 1, 2>{}, index_sequence<2, 1, 0>{});pair<A, A> p2(piecewise_construct, t1, t2);cout << p1.first.x << " " << p1.first.y << " " << p1.first.z << " " << p1.second.x << " " << p1.second.y << " " << p1.second.z << endl;cout << p2.first.x << " " << p2.first.y << " " << p2.first.z << " " << p2.second.x << " " << p2.second.y << " " << p2.second.z << endl;return 0;
}

例子的输出结果如下:

1 2 3 4 5 6
1 2 3 6 5 4

到这里就明白了,这两个构造函数是说,不把tuple当作一个类型看待,而是将tuple中的所有元素取出,使用这些元素来构造pair。带有两个index_sequence类型的构造函数,是用这两个sequence来指示,使用tuple里的哪些元素,以及以怎样的顺序,来构造pair的first和second;而带有piecewise_construct_t类型的构造函数其实是前一种的特殊形式,它默认把两个tuple中的所有元素,从头到尾按顺序取出,来构造pair。其实看源码也能发现,这个构造函数的内部实现就是调用了前一个构造函数来完成的。

在STL的实际实现中,这些构造函数的签名并不像前面列出的那么简洁。实际上,STL的每个函数,都希望在编译期间能够尽可能地多做类型检查,并且能确定是否会抛出异常。我们这里以一个构造函数为例,来看看它完整的声明:

template <class _Uty1 = _Ty1, class _Uty2 = _Ty2,enable_if_t<conjunction_v<is_default_constructible<_Uty1>, is_default_constructible<_Uty2>>, int> = 0>
constexpr explicit(!conjunction_v<_Is_implicitly_default_constructible<_Uty1>, _Is_implicitly_default_constructible<_Uty2>>)pair() noexcept(is_nothrow_default_constructible_v<_Uty1>&& is_nothrow_default_constructible_v<_Uty2>) // strengthened: first(), second() {}

首先是一开始template的声明,这里加了一个enable_if_t<bool, T>,它用来进行编译检查,即只有第一个模板参数推导出来结果为true时,后面的T才生效,也就是说,如果检查失败,就不存在对应的T,这里的template声明就是非法,编译期间就会报错。

那么它要检查的是什么呢?可以看到是一个conjunction_v<T…>,它挨个对里面的参数进行检查,只有当所有的参数检查通过时,才会返回true。conjunction_v里包含is_default_constructible<_Uty1>is_default_constructible<_Uty2>这两个参数,从字面意思就能看出,这个template是用来判断某个类型是否有默认构造函数的。那么这下就很清楚了,只有传入pair的两个类型都存在默认构造函数时,这个pair才有默认构造函数,否则编译时就会报错。为了验证这一点,我们写个例子来尝试一下:

#include <iostream>
#include <utility>
#include <tuple>using namespace std;struct A
{A() = default;A(int x) {}
};struct B
{B() = delete;B(int x) {}
};int main()
{pair<A, A> p1; // okpair<A, B> p2; // errorpair<A, B> p3(1, 2); // okreturn 0;
}

例子中B类型没有默认构造函数,所以p2编译就会直接失败,而由于A类型和B类型都有接受一个int类型的构造函数,因此p3可以编译成功。

接下来我们发现一个explicit(bool)表达式,它的含义是说如果表达式返回值为true,那么explicit就会生效,也就是说这个默认构造函数是explicit的,必须显式构造不能隐式转换。不难发现,只要传入pair的两个类型任意一个的默认构造函数是explicit的,那么pair的这个默认构造函数就是explicit的,这一点也很好理解。同样我们以例子进行佐证:

#include <iostream>
#include <utility>
#include <tuple>using namespace std;struct A
{A() = default;
};struct B
{explicit B() = default;
};int main()
{pair<A, A> p1 = {}; // okpair<A, B> p2 = {}; // errorreturn 0;
}

用MSVC编译时,报错信息还贴心地告诉了我们这个构造函数是explicit的:

test.cpp(20): error C2512: 'std::pair<A,B>': no appropriate default constructor available
test.cpp(20): note: Constructor for struct 'std::pair<A,B>' is declared 'explicit'

最后就是noexcept声明了,同理,只有当pair的两个类型默认构造函数都不抛出异常时,它才不会抛出异常。

有了构造函数之后,就要有与之匹配的赋值操作,pair重载的赋值操作符大概也有以下几种:

template <class _Ty1, class _Ty2>
struct pair {pair& operator=(const volatile pair&) = delete;template <class _Myself = pair>pair& operator=(_Identity_t<const _Myself&> _Right) {first  = _Right.first;second = _Right.second;return *this;}template <class _Myself = pair>pair& operator=(_Identity_t<_Myself&&> _Right) {first  = _STD forward<_Ty1>(_Right.first);second = _STD forward<_Ty2>(_Right.second);return *this;}template <class _Other1, class _Other2>pair& operator=(const pair<_Other1, _Other2>& _Right) {first  = _Right.first;second = _Right.second;return *this;}template <class _Other1, class _Other2>pair& operator=(pair<_Other1, _Other2>&& _Right) {first  = _STD forward<_Other1>(_Right.first);second = _STD forward<_Other2>(_Right.second);return *this;}};

基本和构造函数一一对应,这里就不再赘述了。pair还提供了swap操作,相同类型和不同类型的两个pair都可以进行swap,交互彼此的值:

template <class _Ty1, class _Ty2>
struct pair {void swap(pair& _Right) {using _STD swap;if (this != _STD addressof(_Right)) {swap(first, _Right.first); // intentional ADLswap(second, _Right.second); // intentional ADL}}
};template <class _Ty1, class _Ty2>
void swap(pair<_Ty1, _Ty2>& _Left, pair<_Ty1, _Ty2>& _Right) {_Left.swap(_Right);
}

swap不同类型的pair,需要把后一个pair类型转换为前一个类型。swap内部实现也很简单,就是分别调用每个类型的swap,ADL机制保证了这一点。

pair类型还提供了比较机制,它会首先拿第一个类型进行比较,如果第一个元素不相等,那么比较结果就是最终的结果,如果相等才会比较第二个元素。这意味着只有当两个元素都相等时,两个pair对象才会视为相等。

template <class _Ty1, class _Ty2, class _Uty1, class _Uty2>
constexpr bool operator==(const pair<_Ty1, _Ty2>& _Left, const pair<_Uty1, _Uty2>& _Right) {return _Left.first == _Right.first && _Left.second == _Right.second;
}template <class _Ty1, class _Ty2, class _Uty1, class _Uty2>
constexpr common_comparison_category_t<_Synth_three_way_result<_Ty1, _Uty1>,_Synth_three_way_result<_Ty2, _Uty2>>operator<=>(const pair<_Ty1, _Ty2>& _Left, const pair<_Uty1, _Uty2>& _Right) {if (auto _Result = _Synth_three_way{}(_Left.first, _Right.first); _Result != 0) {return _Result;}return _Synth_three_way{}(_Left.second, _Right.second);
}

C++ 20提出了spaceship <=> 操作符,可以不再写重复的比较代码了,<=>操作符返回的对象有以下的性质:

当左操作数 < 右操作数时,对象 < 0;

当左操作数 > 右操作数时,对象 > 0;

当左操作数 = 右操作数时,对象 = 0。

还是以一个例子来验证:

#include <iostream>
#include <utility>
#include <tuple>using namespace std;int main()
{pair<int, int> p1(1, 2);pair<int, int> p2(3, 4);auto comp = (p1 <=> p2);if(comp < 0){cout << "p1 < p2" << endl;}else if(comp > 0){cout << "p1 > p2" << endl;}else{cout << "p1 == p2" << endl;}return 0;
}

记得要用C++20标准编译哦,不然编译就过不去了,运行结果如下:

>cl test.cpp /std:c++20
>test.exe
p1 < p2

最后,pair还支持便捷函数make_pair构造出对象。注意如果两个参数类型是reference_wrapper,则需要取它们的引用类型作为pair的类型,make_pair本身的实现很简单,就是调用一下pair的构造函数即可。

template <class _Ty>
struct _Unrefwrap_helper { // leave unchanged if not a reference_wrapperusing type = _Ty;
};template <class _Ty>
struct _Unrefwrap_helper<reference_wrapper<_Ty>> { // make a reference from a reference_wrapperusing type = _Ty&;
};// decay, then unwrap a reference_wrapper
template <class _Ty>
using _Unrefwrap_t = typename _Unrefwrap_helper<decay_t<_Ty>>::type;_EXPORT_STD template <class _Ty1, class _Ty2>
_NODISCARD constexpr pair<_Unrefwrap_t<_Ty1>, _Unrefwrap_t<_Ty2>> make_pair(_Ty1&& _Val1, _Ty2&& _Val2) noexcept(is_nothrow_constructible_v<_Unrefwrap_t<_Ty1>, _Ty1>&&is_nothrow_constructible_v<_Unrefwrap_t<_Ty2>, _Ty2>) /* strengthened */ {// return pair composed from argumentsusing _Mypair = pair<_Unrefwrap_t<_Ty1>, _Unrefwrap_t<_Ty2>>;return _Mypair(_STD forward<_Ty1>(_Val1), _STD forward<_Ty2>(_Val2));
}

看个例子,就能明白这里特殊处理reference_wrapper的作用了:

#include <iostream>
#include <utility>using namespace std;template<typename T>
void f(T&& x)
{}int main()
{int x = 1;auto p1 = make_pair(x, x);p1.first = 3;cout << "x " << x << endl;int& y = x;auto p2 = make_pair(y, y);p2.first = 5;cout << "x " << x << endl;auto p3 = make_pair(ref(x), ref(x));p3.first = 7;cout << "x " << x << endl;pair<int&, int&> p4(y, y);p4.first = 9;cout << "x " << x << endl;return 0;
}

例子的输出结果如下:

x 1
x 1
x 7
x 9

有点令人意外的是p2,它传入的参数明明是int&,但pair的参数类型却是int。这是因为make_pair返回的参数类型是_Unrefwrap_t,而它会先调用一次decay_t把引用类型给摘掉,虽然一开始_Ty1_Ty2都会被推导为int&,但是经过decay_t之后它们就退化成了int,传给了pair。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/170411.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#异常处理-throw语句

throw语句是我们手动引发异常的一个语句。 在程序执行过程中&#xff0c;当某些条件不符合我们的要求时&#xff0c;那么我们就可以使用throw语句手动抛出异常&#xff0c;那么就可以在异常发生的地方终止当前代码块的执行&#xff0c;此时我们就可以把控制权传递给调用堆栈中…

ssm+vue的物资物流系统的设计与实现(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的物资物流系统的设计与实现&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体…

目标检测YOLO系列从入门到精通技术详解100篇-【目标检测】SLAM(基础篇)(二)

目录 知识储备 概率论基础 边缘概率 联合概率和独立 独立与条件独立

JDK源码系列:StringBuffer与StringBuilder对比

一、源码分析StringBuffer与StringBuilder的区别 1、StringBuffer是多线程安全的&#xff0c;StringBuilder是多线程不安全的 多线程安全指的是 多个线程同时对一个对象进行append 等操作&#xff0c;不会出现覆盖、丢失的情况。 看下StringBuffer是如何做到多线程安全的&#…

BART 并行成像压缩感知重建:联合重建

本文使用 variavle-density possion-disc 采样的多通道膝盖数据进行并行重建和压缩感知重建。 0 数据欠采样sampling pattern 1 计算ESPIRiT maps % A visualization of k-space dataknee = readcfl(data/knee); ksp_rss = bart(rss 8, knee);ksp_rss = squeeze(ksp_rss); figu…

基于单片机的肺活量检测系统(论文+源码)

1.系统设计 在基于单片机的肺活量检测系统中&#xff0c;在硬件上整个系统通过利用主控制器STC89C52单片机来实现对整个系统进行控制的功能&#xff0c;通过采用LCD1602实现实时液晶显示数据的功能&#xff0c;通过肺活量传感器XGZP6847ADC0832实现监测肺活量的工作&#xff0…

终端移动性管理

联系前面所学的知识我们知道&#xff0c;移动性管理主要分为两大类&#xff1a;空闲状态下的移动性管理、连接状态下的移动性管理。我们今天来详细了解他们的工作原理~ 目录 移动性管理分类 1、空闲状态下的移动性管理 2、连接状态下的移动性管理 手机选择天线的原则 4G天…

使用Kibana让es集群形象起来

部署Elasticsearch集群详细步骤参考本人&#xff1a; https://blog.csdn.net/m0_59933574/article/details/134605073?spm1001.2014.3001.5502https://blog.csdn.net/m0_59933574/article/details/134605073?spm1001.2014.3001.5502 kibana部署 es集群设备 安装软件主机名…

python-面试重点问题

面试时&#xff0c;关于Python的问题可能涉及到语法、数据结构、算法、面向对象编程、模块和库等方面。以下是一些可能成为面试重点的知识点&#xff1a; 基本语法&#xff1a; - 变量、数据类型&#xff08;整数、浮点数、字符串、列表、元组、字典等&#xff09; 在Python中…

Centos 7 在线安装(RPM) PostgreSQL 14 15 16

目录 一、官网下载地址二、检查系统是否安装其他版本PostgreSQL数据库三、安装数据库四、配置数据库(默认方式一)4.1初始化用户密码4.2修改postgresql.conf文件4.3修改pg_hba.conf文件五、修改默认存储路径六、配置防火墙七、生产环境优化(待完善)八、启用SSL加密(待验证)九…

SELinux零知识学习三十一、SELinux策略语言之角色和用户(2)

接前一篇文章:SELinux零知识学习三十、SELinux策略语言之角色和用户(1) 三、SELinux策略语言之类型强制 SELinux提供了一种依赖于类型强制(类型增强,TE)的基于角色的访问控制(Role-Based Access Control),角色用于组域类型和限制域类型与用户之间的关系,SELinux中的…

Kafka系列 - 生产者客户端架构以及3个重要参数

整体架构 整个生产者客户端由两个县城协调运行&#xff0c;这两个线程分别为主线程和Sender线程&#xff08;发送线程&#xff09;。 主线程中由KafkaProducer创建消息&#xff0c;然后通过可能的拦截器&#xff0c;序列化器和分区器之后缓存到消息累加器&#xff08;RecordAc…

第十六章 解读深度学习中Batch Size、Iterations和Epochs(工具)

训练网络之前有很多参数要设置&#xff0c;不了解各个参数的含义就没法合理地设置参数值&#xff0c;训练效果也会因此大受影响。本篇博客记录一下网络训练里的Batch Size、Iterations和Epochs怎么理解。 一、引言 首先要了解一下为什么会出现Batch Size这个概念。深度学习算…

nodejs+vue+python+PHP+微信小程序-健身俱乐部在线管理平台的设计与实现-安卓-计算机毕业设计

随着经济的发展、财富的累积&#xff0c;人们生活水平、生活质量大幅度提高&#xff0c;生活环境得到明显改善&#xff0c;但是竞争激烈、人们生活压力大、生活节奏快加上饮食习惯和生活方式不合理导致国内 亚健康人群逐年增多。统计数据表明当前我国亚健康人群比例已经超过了7…

【LeeCode】844.比较含退格的字符串

给定 s 和 t 两个字符串&#xff0c;当它们分别被输入到空白的文本编辑器后&#xff0c;如果两者相等&#xff0c;返回 true 。# 代表退格字符。 注意&#xff1a;如果对空文本输入退格字符&#xff0c;文本继续为空。 解&#xff1a;同时从后向前遍历S和T&#xff08;i初始为…

VScode

一、VSCode设置中文 1、首先我们打开vscode&#xff0c;进入编辑页面后点击左边栏的图示图标进入“EXTENSIONS”面板 2、进入后&#xff0c;在上方搜索“Chinese”&#xff0c;搜索到中文&#xff08;简体&#xff09;后&#xff0c;点击“install”按钮。 3、等待自动下载安装…

【一文讲清楚 Anaconda 相关环境配置】

文章目录 0 前言1 Package 与环境1.1 module1.2 package1.3 环境 2 Conda、Miniconda、Anaconda和Pip & PyPI2.1 Conda2. 2 Miniconda2.3 Anaconda2.3.1 Anaconda Navigator2.3.2 Anaconda PowerShell Prompt & Anaconda Prompt2.3.3 Jupyter notebook 2.4 Pip & P…

【Layui】动态时间线

官方时间线 代码<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">

深度学习第二天:RNN循环神经网络

☁️主页 Nowl &#x1f525;专栏《机器学习实战》 《机器学习》 &#x1f4d1;君子坐而论道&#xff0c;少年起而行之 文章目录 介绍 记忆功能对比展现 任务描述 导入库 处理数据 前馈神经网络 循环神经网络 编译与训练模型 模型预测 可能的问题 梯度消失 梯…

2023/11/24JAVAweb学习

age只会执行成立的,show其实都展示了,通过display不展示 使用Vue,必须引入Vue.js文件 假如运行报错,以管理员身份打开vscode,再运行 ------------------------------------------------------------------- 更改端口号