浅析C++标准库元组(tuple)源码

一、什么是元组

元组不是什么新鲜东西,在数学、python语言还有我们今天要说的C++都有元组。

简单地说,元组就是一组东西,例如,在讲代数拓扑的时候,经常把拓扑空间X和其中一点x作为一个偶对(X, x),这其实就是个元组,点的坐标也可以看成一个元组。C++中的元组(tuple)是这个样子的:

std::tuple<int, std::string> tu{ 2,"12iop" };

一个tuple可以包含不同类型的成员,例如上面的tu包含一个int和一个字符串。

二、用法

在考察源码之前,我们必须先知道它的用法。

要想使用tuple,要包含头文件<tuple>:

#include <tuple>

tuple实际上是一个有可变参数的类模板,使用的时候,传入若干个参数将其特化。

struct Point
{
 int x;
 int y;
};

void main()
{
 std::tuple<int, std::string> t1{ 1,"qwer" }; // 一个由int和字符串组成的tuple
 constexpr std::tuple<int, void*> t2{ 1,nullptr }; // 一个由int和void*组成的tuple
 std::tuple<int, Point> t3{ 1,{20,89} }; // 一个由int和Point结构体组成的tuple
 std::tuple<int, char, std::string> t4{ 1,'t',"qwer" }; // 一个由int、char、字符串组成的tuple
}

上面的代码中,我用constexpr修饰了t2,这是完全正确的,std::tuple的构造函数是constexpr的。

获取tuple中的值,用std::get。这不是函数,而是函数模板,我们需要传入size_t类型的变量将其特化,或者传入一个类型,告诉它我们需要取出元组中的哪个类型的成员。

struct Point
{
 int x;
 int y;
};

void main()
{
 std::tuple<int, std::string> t1{ 1,"qwer" };
 constexpr std::tuple<int, void*> t2{ 10,nullptr };
 std::tuple<int, Point> t3{ 1,{20,89} };
 std::tuple<int, char, std::string> t4{ 1,'t',"qwer" };

 std::cout << std::get<0>(t1) << std::endl; // 1

 constexpr int n2 = std::get<0>(t2);
 std::cout << n2 << std::endl; // 10

 auto s = std::get<char>(t4);
 std::cout << s << std::endl; // t
}

std::get也是constexpr的,所以n2也是一个编译时的常量。

我们通过get<char>的方式得到了s,它是char类型的变量。std::get<T>可以从tuple中获取到第一个类型为T的成员。

tuple也可以用【==】和【!=】比较是否相等:

std::tuple<int, std::string> t5{ 1,"qwer" };

if (t1 == t5)
{
 std::cout << "==" << std::endl;
}

介绍tuple的用法不是本文的主要内容,故到此为止。有兴趣的同学可以自行查阅资料。

接下来,是时候考察一看源码了。

三、源码分析

tuple是个可变参数的类模板:

template<typename... _Types>
class tuple;

这是对类模板的声明。

接下来,实现参数个数为零的空tuple。

3.1 tuple<>

struct allocator_arg_t
{};

template<>
class tuple<>
{
public:
 typedef tuple<> _Myt;

 constexpr tuple() noexcept
 {}

 template<typename _Alloc>
 tuple(allocator_arg_t, const _Alloc&) noexcept
 {}

 constexpr tuple(const tuple&) noexcept
 {}

 template<class _Alloc>
 tuple(allocator_arg_t, const _Alloc&, const _Myt&) noexcept
 {}

 void swap(_Myt&) noexcept
 {}

 constexpr bool _Equals(const _Myt&) const noexcept
 {
 return true;
 }

 constexpr bool _Less(const _Myt&) const noexcept
 {
 return false;
 }
};

allocator_arg_t是个空的结构体,暂时不管它。_Myt就是tuple<>自己,这样写起来方便一些。

tuple<>定义了空的构造函数和拷贝构造函数(空tuple没什么可做的)。

成员函数swap用于与另一个tuple<>交换内容,因为没什么可交换的,函数体当然是空的。

_Equals用来判断两个tuple<>是否相等,它返回true,这是显然的(所有的tuple<>都是一个样子)。

_Less从函数名看,是为了比较大小,但如果遇到没有重载<的类型呢?暂时不管它。

有了空tuple的定义,就可以定义非空的tuple。

3.2 非空的tuple

template<class _This,
class... _Rest>
class tuple<_This, _Rest...>
 : private tuple<_Rest...>
{
 // 内容
}

n(>0)个元素的tuple私有继承了n-1个元素的tuple。显然这是一种递归定义,最终会递归到tuple<>,而tuple<>是已经定义好了得。

例如,tuple<int, char, short>私有继承了tuple<char, short>,而tuple<char, short>又私有继承了tuple<short>,tuple<short>私有继承了tuple<>。由于私有继承可以实现“has-a”功能,所以,这样的方式可以将不同类型的对象组合在一起。如下图:

那么,tuple是如何存储其中的元素呢?

template<class _This,
class... _Rest>
class tuple<_This, _Rest...>
 : private tuple<_Rest...>
{ // recursive tuple definition
public:
 typedef _This _This_type;
 typedef tuple<_This, _Rest...> _Myt;
 typedef tuple<_Rest...> _Mybase;
 static const size_t _Mysize = 1 + sizeof...(_Rest);

 _Tuple_val<_This> _Myfirst; // 存储的元素
}

原来,它有个成员叫_Myfirst,它就是用来存储_This类型的变量的。你会看到_Myfirst的类型不是_This而是_Tuple_val<_This>,其实,_Tuple_val又是一个类模板,它的代码这里就不展开了,简而言之,它的作用是存储一个tuple中的变量。_Myfirst._Val才是真正的元素。

这个tuple只存储一个元素,类型为_Rest...的其他元素存在基类_MyBase即tuple<_Rest...>中。我们仍然以tuple<int, char, short>为例,tuple<int, char, short>存储了一个int,有基类tuple<char, short>;而tuple<char, short>存储了一个char,有基类tuple<short>;tuple<short>存储了一个short,有基类tuple<>;tuple没有基类也不存储任何元素。

3.3 构造函数

tuple的构造函数没什么可说的,就是初始化_Myfirst和_MyBase,当然,_MyBase也要进行么一个过程,直到tuple<>。

 constexpr tuple(): _Mybase(), _Myfirst()
 {}

 constexpr explicit tuple(const _This& _This_arg,
 const _Rest&... _Rest_arg)
 : _Mybase(_Rest_arg...),
 _Myfirst(_This_arg)
 {}

 tuple(const _Myt&) = default;
 tuple(_Myt&&) = default;

它还提供了默认拷贝构造函数和移动构造函数(移动语义是C++11中新增的特性,请自行查阅资料)。其实,它还有很多构造函数,写起来挺热闹,无非就是用不同的方式为它赋初值,故省略。

3.4 部分成员函数

tuple重载了赋值符号(=),这样,tuple之间是可以赋值的

 template<class... _Other>
 _Myt& operator=(const tuple<_Other...>& _Right)
 { // assign by copying same size tuple
 _Myfirst._Val = _Right._Myfirst._Val;
 _Get_rest() = _Right._Get_rest();
 return (*this);
 }

赋值符号返回左边的引用,这种行为和C++的内置类型是一致的。_Get_rest是tuple的成员函数,作用是把除了_Myfirst之外的那些元素拿出来。

接下来是成员函数_Equals,

template<class... _Other>
constexpr bool _Equals(const tuple<_Other...>& _Right) const
{
 static_assert(_Mysize == sizeof...(_Other), "comparing tuple to object with different size");
 return (_Myfirst._Val == _Right._Myfirst._Val && _Mybase::_Equals(_Right._Get_rest()));
}

其中进行了静态断言,如果两个tuple的元素个数不相同,会引发一个编译时的错误。如果对应的类型不能用==进行比较,在模板特化时也会引发编译期的错误,例如,tuple<std::string, int>不能和tuple<int, char>比较,因为std::string和int是不能用==进行比较的。

前面提到的_Get_rest在这里:

_Mybase& _Get_rest() noexcept
{
 return (*this);
}

constexpr const _Mybase& _Get_rest() const noexcept
{
 return (*this);
}

它返回对基类的引用。*this的类型虽然是_Myt,但根据C++语法(可以把派生类的引用赋给对基类的引用),所以这样做是没问题的。

以上就是C++标准库元组(tuple)源码浅析的全部内容,希望对大家的学习有所帮助。

(0)

相关推荐

  • python列表与元组详解实例

    在这章中引入了数据结构的概念.数据结构是通过某种方式组织在一起的数据元素的集合.在python中,最基本的数据结构就是序列.序列中的每个元素被分配一个序号,即元素的位置,也被称为索引.注意:第一个索引是0.1.序列概览python有6种内建的序列:列表,元组,字符串,Unicode字符串,buffer对象和xrange对象. 这里重点介绍列表和元组.列表和元组主要区别在于,列表可以修改,元组不可修改.一般来说,在几乎所有情况下列表都可以代替元组.在需要操作一组数值的时候,序列很好用: 复制代码

  • Python中列表、字典、元组、集合数据结构整理

    本文详细归纳整理了Python中列表.字典.元组.集合数据结构.分享给大家供大家参考.具体分析如下: 列表: 复制代码 代码如下: shoplist = ['apple', 'mango', 'carrot', 'banana'] 字典: 复制代码 代码如下: di = {'a':123,'b':'something'} 集合: 复制代码 代码如下: jihe = {'apple','pear','apple'} 元组: 复制代码 代码如下: t = 123,456,'hello' 1.列表 空

  • 初步讲解Python中的元组概念

    元组是不可变的Python对象序列.元组的序列就像列表.唯一的区别是,元组不能被改变,即元组是不可被修改.元组使用小括号,而列表使用方括号. 创建一个元组很简单,只要把不同的逗号分隔值,可以把括号中的这些逗号来分隔每个值.例如: tup1 = ('physics', 'chemistry', 1997, 2000); tup2 = (1, 2, 3, 4, 5 ); tup3 = "a", "b", "c", "d"; 空的元

  • Python中的tuple元组详细介绍

    Tuple 是不可变 list. 一旦创建了一个 tuple 就不能以任何方式改变它. Tuple 与 list 的相同之处 定义 tuple 与定义 list 的方式相同, 除了整个元素集是用小括号包围的而不是方括号. Tuple 的元素与 list 一样按定义的次序进行排序. Tuples 的索引与 list 一样从 0 开始, 所以一个非空 tuple 的第一个元素总是 t[0]. 负数索引与 list 一样从 tuple 的尾部开始计数. 与 list 一样分片 (slice) 也可以使

  • 跟老齐学Python之有点简约的元组

    关于元组,上一讲中涉及到了这个名词.本讲完整地讲述它. 先看一个例子: >>>#变量引用str >>> s = "abc" >>> s 'abc' >>>#如果这样写,就会是... >>> t = 123,'abc',["come","here"] >>> t (123, 'abc', ['come', 'here']) 上面例子中看到的变

  • C++标准库中sstream与strstream的区别详细解析

    在C++有两种字符串流,一种在sstream中定义,另一种在strstream中定义.它们实现的东西基本一样. strstream里包含class strstreambuf;class istrstream;class ostrstream;class strstream;它们是基于C类型字符串char*编写的 sstream中包含class istringstream;class ostringstream;class stringbuf;class stringstream;class --

  • python元组操作实例解析

    本文实例讲述了python元组操作方法,分享给大家供大家参考.具体分析如下: 一般来说,python的函数用法挺灵活的,和c.php的用法不太一样,和js倒是挺像的. 在照着操作时,可以发现一个很神奇的现象: >>> t = (1, 3, 'b') >>> q = t + ((3, 'abc')) >>> q (1, 3, 'b', 3, 'abc') 这里我预料的应该是(1, 3, 'b', (3, 'abc')),但是结果却是(1, 3, 'b',

  • Python入门篇之列表和元组

    列表和元组的主要区别在于,列表可以修改,元组则不能.一般情况下,在几乎所有的情况下列表都可以代替元组 例如:使用序列可以表示数据库中一个人的信息(姓名,年龄) 复制代码 代码如下: >>> edward=['Edward Gumby',42] 序列还可以包含其他序列 复制代码 代码如下: >>> edward=['Edward Gumby',42] >>> john=['John Smith',50] >>> database=[ed

  • 浅析C++标准库元组(tuple)源码

    一.什么是元组 元组不是什么新鲜东西,在数学.python语言还有我们今天要说的C++都有元组. 简单地说,元组就是一组东西,例如,在讲代数拓扑的时候,经常把拓扑空间X和其中一点x作为一个偶对(X, x),这其实就是个元组,点的坐标也可以看成一个元组.C++中的元组(tuple)是这个样子的: std::tuple<int, std::string> tu{ 2,"12iop" }; 一个tuple可以包含不同类型的成员,例如上面的tu包含一个int和一个字符串. 二.用法

  • 浅析python标准库中的glob

    glob 文件名模式匹配,不用遍历整个目录判断每个文件是不是符合. 1.通配符 星号(*)匹配零个或多个字符 import glob for name in glob.glob('dir/*'): print (name) dir/file.txt dir/file1.txt dir/file2.txt dir/filea.txt dir/fileb.txt dir/subdir 列出子目录中的文件,必须在模式中包括子目录名: import glob #用子目录查询文件 print ('Name

  • 浅析PHP7的多进程及实例源码

    准备 我们都知道PHP是单进程执行的,PHP处理多并发主要是依赖服务器或PHP-FPM的多进程及它们进程的复用,但PHP实现多进程也意义重大,尤其是在后台Cli模式下处理大量数据或运行后台DEMON守护进程时,多进程的优势不用多说. PHP的多线程也曾被人提及,但进程内多线程资源共享和分配的问题难以解决.PHP也有多线程想关的扩展 pthreads ,但据说不太稳定,且要求环境为线程安全,所用不多. 以前PHP群里的一位大神曾指导说后台PHP想进阶必然避不开多进程,正好公司里的守护进程也应用了P

  • ahooks整体架构及React工具库源码解读

    目录 引言 React hooks utils 库 ahooks 简介 特点 hooks 种类 ahooks 整体架构 项目启动 整体结构 hooks 总结 引言 本文是深入浅出 ahooks 源码系列文章的第一篇,这个系列的目标主要有以下几点: 加深对 React hooks 的理解. 学习如何抽象自定义 hooks.构建属于自己的 React hooks 工具库. 培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择. 注:本系列对 ahooks 的源码解析是基于 v3.3.13.自己

  • Redis源码设计剖析之事件处理示例详解

    目录 1. Redis事件介绍 2. 事件的抽象 2.1 文件事件结构 2.2 时间事件结构 2.3 事件状态结构 3. 事件的实现 1. Redis事件介绍 Redis服务器是一个事件驱动程序,所谓事件驱动就是输入一条命令并且按下回车,然后消息被组装成Redis协议的格式发送给Redis服务器,这个时候就会产生一个事件,Redis服务器会接收改命令,处理该命令和发送回复,而当我们没有与服务器进行交互时,服务器就会处于阻塞等待状态,它会让出CPU然后进入睡眠状态,当事件触发时,就会被操作系统唤醒

  • Golang标准库unsafe源码解读

    目录 引言 unsafe包 unsafe构成 type ArbitraryType int type Pointer *ArbitraryType 灵活转换 潜在的危险性 正确的使用姿势 错误的使用姿势 func Sizeof(x ArbitraryType) uintptr func Offsetof(x ArbitraryType) uintptr func Alignof(x ArbitraryType) uintptr 引言 当你阅读Golang源码时一定遇到过unsafe.Pointe

  • 详解java实践SPI机制及浅析源码

    1.概念 正式步入今天的核心内容之前,溪源先给大家介绍一下关于SPI机制的相关概念,最后会提供实践源代码. SPI即Service Provider Interface,属于JDK内置的一种动态的服务提供发现机制,可以理解为运行时动态加载接口的实现类.更甚至,大家可以将SPI机制与设计模式中的策略模式建立联系. SPI机制: 从上图中理解SPI机制:标准化接口+策略模式+配置文件: SPI机制核心思想:系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编

  • 深入浅析knockout源码分析之订阅

    Knockout.js是什么? Knockout是一款很优秀的JavaScript库,它可以帮助你仅使用一个清晰整洁的底层数据模型(data model)即可创建一个富文本且具有良好的显示和编辑功能的用户界面.任何时候你的局部UI内容需要自动更新(比如:依赖于用户行为的改变或者外部的数据源发生变化),KO都可以很简单的帮你实现,并且非常易于维护. 一.主类关系图 二.类职责 2.1.observable(普通监控对象类) observable(他其是一个function)的内部实现: 1.首先声

  • VisualStudio2019构建C/C++静态库和动态库dll的问题 附源码

    1. 静态库和动态库 1.1. 静态链接库 举个例子,假如你在编写一个C++工程,根据业务逻辑,这个工程需要用到一些工具类,例如集合操作的工具类(暂且叫他collection_utils),于是你直接定义一个collection_utils.h头文件和一个collection_utils.cpp文件,在头文件中写一些工具函数的定义,在cpp文件中写函数的实现逻辑:如下所示: //---------------collection_utils.h--------------------------

  • 浅析C++元组tuple类型

    目录 介绍 tuple的定义及初始化 tuple的使用 成员访问 获取tuple信息 拼接tuple 交换tuple tuple解包 tuple比较 tuple遍历 tuple开发时的应用 介绍 元组tuple是C++的一个模板,不同tuple类型的成员类型也不相同,但是一个tuple可以有任意数量的成员. 每个tuple类型的成员个数是一定的,但是不同的tuple成员个数可以不同. tuple的定义及初始化 使用tuple要引入tuple头文件 #include <tuple> tuple定

随机推荐