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;

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

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

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。

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。

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”功能,所以,这样的方式可以将不同类型的对象组合在一起。

时间: 2024-10-14 00:54:16

C++标准库元组(tuple)源码浅析的相关文章

python语言线程标准库threading.local源码解读

本段源码可以学习的地方: 1. 考虑到效率问题,可以通过上下文的机制,在属性被访问的时候临时构建: 2. 可以重写一些魔术方法,比如 __new__ 方法,在调用 object.__new__(cls) 前后进行属性的一些小设置: 3. 在本库中使用的重写魔术方法,上下文这两种基础之上,我们可以想到函数装饰器,类装饰器,异常捕获,以及两种上下文的结构: 灵活运用这些手法,可以让我们在代码架构上更上一层,能够更加省时省力. 1 from weakref import ref # ref用在了构造大

go标准库-log包源码学习

log包是go语言提供的一个简单的日志记录功能,其中定义了一个结构体类型 Logger,是整个包的基础部分,包中的其他方法都是围绕这整个结构体创建的. Logger结构 Logger结构的定义如下: type Logger struct { mu sync.Mutex prefix string flag int out io.Writer buf []byte } mu 是sync.Mutex,它是一个同步互斥锁,用于保证日志记录的原子性. prefix 是输入的日志每一行的前缀 flag 是

ReactiveCocoa2 源码浅析

ReactiveCocoa2 源码浅析 标签(空格分隔): ReactiveCocoa iOS Objective-C ? 开车不需要知道离合器是怎么工作的,但如果知道离合器原理,那么车子可以开得更平稳. ReactiveCocoa 是一个重型的 FRP 框架,内容十分丰富,它使用了大量内建的 block,这使得其有强大的功能的同时,内部源码也比较复杂.本文研究的版本是2.4.4,小版本间的差别不是太大,无需担心此问题. 这里只探究其核心 RACSignal 源码及其相关部分.本文不会详细解释里

【Spark】Stage生成和Stage源码浅析

引入 上一篇文章<DAGScheduler源码浅析>中,介绍了handleJobSubmitted函数,它作为生成finalStage的重要函数存在,这一篇文章中,我将就DAGScheduler生成Stage过程继续学习,同时介绍Stage的相关源码. Stage生成 Stage的调度是由DAGScheduler完成的.由RDD的有向无环图DAG切分出了Stage的有向无环图DAG.Stage的DAG通过最后执行的Stage为根进行广度优先遍历,遍历到最开始执行的Stage执行,如果提交的St

转:Spring FactoryBean源码浅析

http://blog.csdn.net/java2000_wl/article/details/7410714 在Spring BeanFactory容器中管理两种bean 1.标准Java Bean 2,另一种是工厂Bean,   即实现了FactoryBean接口的bean  它不是一个简单的Bean 而是一个生产或修饰对象生成的工厂Bean 在向Spring容器获得bean时  对于标准的java Bean  返回的是类自身的实例 而FactoryBean 其返回的对象不一定是自身类的一

Spring FactoryBean源码浅析

在Spring BeanFactory容器中管理两种bean 1.标准Java Bean 2,另一种是工厂Bean,   即实现了FactoryBean接口的bean  它不是一个简单的Bean 而是一个生产或修饰对象生成的工厂Bean 在向Spring容器获得bean时  对于标准的java Bean  返回的是类自身的实例 而FactoryBean 其返回的对象不一定是自身类的一个实例,返回的是该工厂Bean的getObject方法所返回的对象 一个简单的例子 [java] view pla

Gradle 庖丁解牛(构建源头源码浅析)

1 背景 陆陆续续一年多,总是有人问 Gradle 构建,总是发现很多人用 Gradle 是迷糊状态的,于是最近准备来一个"Gradle 庖丁解牛"系列,一方面作为自己的总结,一方面希望真的能达到标题所示效果,同时希望通过该系列达到珍惜彼此时间的目的,因为目前市面上关于 Gradle 的教程都是在教怎么配置和怎么编写插件,很少有说明 Gradle 自己到底是个啥玩意的,还有是如何工作的,本系列以官方 release 3.4 版本为基础. 废话不多说,标题也表明了本篇所总结的内容 --

Android应用Loaders全面详解及源码浅析

1 背景 在Android中任何耗时的操作都不能放在UI主线程中,所以耗时的操作都需要使用异步实现.同样的,在ContentProvider中也可能存在耗时操作,这时也该使用异步操作,而3.0之后最推荐的异步操作就是Loader.它可以方便我们在Activity和Fragment中异步加载数据,而不是用线程或AsyncTask,他的优点如下: 提供异步加载数据机制: 对数据源变化进行监听,实时更新数据: 在Activity配置发生变化(如横竖屏切换)时不用重复加载数据: 适用于任何Activit

Gradle 庖丁解牛(构建生命周期核心委托对象创建源码浅析)

[工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果.私信联系我] 1 背景 上一篇<Gradle 庖丁解牛(构建源头源码浅析)>我们分析了 Gradle 框架自身初始化(非构建生命周期初始化)的核心流程,这一篇我们续着前面的分析继续(如果没看过前一篇的建议先去看前一篇,因为这一系列存在非常高的关联性).上一篇说到当我们执行 gradle taskName 命令后经过一系列艰难的框架初始化最终走到了 DefaultGradleLaunch

【Spark】DAGScheduler源码浅析2

引入 上一篇文章DAGScheduler源码浅析主要从提交Job的流程角度介绍了DAGScheduler源码中的重要函数和关键点,这篇DAGScheduler源码浅析2主要参考fxjwind的Spark源码分析 – DAGScheduler一文,介绍一下DAGScheduler文件中之前没有介绍的几个重要函数. 事件处理 在Spark 1.0版本之前,在DAGScheduler类中加入eventQueue私有成员,设置eventLoop Thread循环读取事件进行处理.在Spark 1.0源码