C++ 学习系列 -- tuple 原理

一  可变参数模板 variadic template

        前面的章节 C++ 学习系列 -- 模板 template-CSDN博客 我们介绍了 c++ 中的模板概念,本章则在其基础上介绍了新的概念  可变参数模板 variadic template ,顾名思义,可变参数模板意思为模板参数的类型与数量是变化的,比如:

template<typename ...Args>
void print(Args... args);template<typename ...Args>
class my_class;

1.1  可变参数模板函数

#include<iostream>// 递归终止调用的 Print 模板参数个数为 0
void Print()
{std::cout << "  " << std::endl;
}template<typename T, typename ... Args>
void Print(T t, Args...args)
{std::cout << t << " "; // 打印出参数  t 的值Print(args...); // 递归调用 Print,将除了 t 剩下的一包参数丢入 Print
}int main()
{Print("abc", 'a', 1.0, 66);return 0;
}

输出:

1.2 可变参数模板递归继承类

// my_class.h
#include<iostream>template<typename ...Args>
class MyClass;// 递归类终止时,template 的参数包为空
template<>
class MyClass<>
{
public:MyClass(){std::cout << "MyClass constructor. " << std::endl;}
};// 递归继承 MyClass 类,基类比派生类的模板参数包少一个 参数 T
template<typename T, typename ...Args>
class MyClass<T, Args...> : private MyClass<Args...>
{
public:MyClass(T t, Args... args):data(t),MyClass<Args...>(args...){std::cout << "MyClass constructor sizeof(Args) " << sizeof... (Args) << ", data: " << data << std::endl;}private:T data;
};// main.cpp
#include"my_class.h"int main()
{MyClass<double, float, int, long, std::string> my_class(1.22, 1.6, 66, 88, "abcd");return 0;
}

输出:

template<>
class MyClass<>;

由输出可以看出,递归继承可变参数的类的模板参数包大小,从 4 -> 3 -> 2 -> 1 -> 0

,最后递归终止时调用的是一个空模板参数包的类 

二  tuple 

1.1  tuple 简介

       在 tuple出现之前,c++ 中的容器,序列容器:vector、deque、list ,关联容器:set、map 等,所存储的元素类型都是单一的(map 的 所有的 key 类型是相同的, 所有的 value 类型是相同的 )。

       如果我们有这样一个需求,需要一个容器或者说用着类似于容器的一个东西,可以存储不同类型的元素,上面的容器是无法满足我们的需求的。考虑到如此,c++ 为我们提供了 pair 类,但是美中不足的是,pair 只能存储两个元素,若是存储两个以上的元素呢? pair 就帮不了我们了。

       在 c++11 之前,前面的需求是无法满足的,c++11则提供了 元组 std::tuple  这个概念(元组在其他类型的语言中也有过,比如 python 中就有元组的概念)  帮助我们实现了这个需求,std::tuple 可以存放任意类型任意个数的元素。

1.2  tuple 原理

    如果让我们来设计元组 std::tuple 的话,底层究竟用什么结构呢?我们可以先复习一下其他一些容器的底层结构:

容器底层结构
vector数组
list链表
map红黑树
unorder_maphash 表

     上述表格中的底层结构都用不了,因为这些底层结构的所有元素类型都是需要一致的。

那让我们抛开底层结构的固化思维,来考虑一下用本章节介绍的 可变模板参数类来实现吧。

    定义变模板参数类,将模板参数包拆分为 第一个模板参数与剩下的模板参数包,在当前类中定义 第一个模板参数的类成员,并在该类构造函数中给该类成员赋值。改了继承一个基类,基类中使用剩下的模板参数包,其余同派生类是相同的。最后,定义一个递归终止的类,递归终止类中无任何模板参数,里面的实现也是空的。

1.3  tuple 源码

    template<std::size_t _Idx, typename _Head>struct _Head_base<_Idx, _Head, false>{constexpr _Head_base(): _M_head_impl() { }constexpr _Head_base(const _Head& __h): _M_head_impl(__h) { }constexpr _Head_base(const _Head_base&) = default;constexpr _Head_base(_Head_base&&) = default;static constexpr _Head&_M_head(_Head_base& __b) noexcept { return __b._M_head_impl; }static constexpr const _Head&_M_head(const _Head_base& __b) noexcept { return __b._M_head_impl; }........._Head _M_head_impl;};// Basis case of inheritance recursion.template<std::size_t _Idx, typename _Head>struct _Tuple_impl<_Idx, _Head>: private _Head_base<_Idx, _Head>{template<std::size_t, typename...> friend class _Tuple_impl;typedef _Head_base<_Idx, _Head> _Base;static constexpr _Head&_M_head(_Tuple_impl& __t) noexcept { return _Base::_M_head(__t); }static constexpr const _Head&_M_head(const _Tuple_impl& __t) noexcept { return _Base::_M_head(__t); }constexpr _Tuple_impl(): _Base() { }explicitconstexpr _Tuple_impl(const _Head& __head): _Base(__head) { }.........
};/*** Contains the actual implementation of the @c tuple template, stored* as a recursive inheritance hierarchy from the first element (most* derived class) to the last (least derived class). The @c Idx* parameter gives the 0-based index of the element stored at this* point in the hierarchy; we use it to implement a constant-time* get() operation.*/template<std::size_t _Idx, typename... _Elements>struct _Tuple_impl;/*** Recursive tuple implementation. Here we store the @c Head element* and derive from a @c Tuple_impl containing the remaining elements* (which contains the @c Tail).*/template<std::size_t _Idx, typename _Head, typename... _Tail>struct _Tuple_impl<_Idx, _Head, _Tail...>: public _Tuple_impl<_Idx + 1, _Tail...>,private _Head_base<_Idx, _Head>{   template<std::size_t, typename...> friend class _Tuple_impl;typedef _Tuple_impl<_Idx + 1, _Tail...> _Inherited;typedef _Head_base<_Idx, _Head> _Base;static constexpr _Head&_M_head(_Tuple_impl& __t) noexcept { return _Base::_M_head(__t); }static constexpr const _Head&_M_head(const _Tuple_impl& __t) noexcept { return _Base::_M_head(__t); }static constexpr _Inherited&_M_tail(_Tuple_impl& __t) noexcept { return __t; }static constexpr const _Inherited&_M_tail(const _Tuple_impl& __t) noexcept { return __t; }constexpr _Tuple_impl(): _Inherited(), _Base() { }explicitconstexpr _Tuple_impl(const _Head& __head, const _Tail&... __tail): _Inherited(__tail...), _Base(__head) { }.........
};/// Primary class template, tuple
template<typename... _Elements>
class tuple : public _Tuple_impl<0, _Elements...>
{typedef _Tuple_impl<0, _Elements...> _Inherited;constexpr tuple(): _Inherited() { }.........constexpr tuple(const _Elements&... __elements): _Inherited(__elements...) { }.........};

 源码解析:

  •  struct  tuple 是 c++ 中对外提供的元组类,通过源码可以看出,class tuple 是一个可变模板参数类,其模板参数可以是多个类型不同的参数,该类继承自 class _Tuple_impl
template<typename... _Elements>
class tuple : public _Tuple_impl<0, _Elements...>;
  • struct  _Tuple_impl 有三个模板参数,_Idx 表示元组中当前元素的下标,从 0 开始,_Head 表示当前存储的元素类型, Tail 表示剩下的模板参数包。该类继承了两个基类,分别是 class _Tuple_impl(就算该类本身,但是其模板参数是不同的),该类不存储元素, _Head_base 是用来存储元素的。 
template<std::size_t _Idx, typename _Head, typename... _Tail>struct _Tuple_impl<_Idx, _Head, _Tail...>: public _Tuple_impl<_Idx + 1, _Tail...>,private _Head_base<_Idx, _Head>;
  • 与递归函数需要终止条件一样,递归类的继承也需要终止类,下面就算递归终止类,在该类中,模板参数有两个 _Idx 表示当前元素的下标,_Head 表示元组中最后一个元素的类型,该类不存储元素,是通过继承的 _Head_base 来实现元素的存储的
template<std::size_t _Idx, typename _Head>struct _Tuple_impl<_Idx, _Head>: private _Head_base<_Idx, _Head>;
  •  struct _Head_base 是 struct _Tuple_impl 的基类,该类又两个模板参数:_Idx 表示元素的下标,_Head 表示当前存储元素的类型,该类的主要作用是在内存中开辟一块内存空间,用来存储成员变量 _Head  _M_head_impl;
    template<std::size_t _Idx, typename _Head>struct _Head_base<_Idx, _Head, false>;

  make_tuple 函数 可以帮用户在使用中只传入参数,来构造一个 tuple, 其源码如下:  

 template<typename... _Elements>constexpr tuple<typename __decay_and_strip<_Elements>::__type...>make_tuple(_Elements&&... __args){typedef tuple<typename __decay_and_strip<_Elements>::__type...>__result_type;return __result_type(std::forward<_Elements>(__args)...);}

  通过  __decay_and_strip 将模板参数类型获取出来,定义返回类型 __result_type  ,实际就是 tuple 类型,再利用万能转发 std::forward 将参数转发给返回 类型 __result_type ,构造一个临时对象 return 。函数的返回类型用 constexpr 修饰,表示编译器就将该函数执行创造出 tuple 对象。

上面大多数代码都能看懂,__decay_and_strip 有点奇怪,源码如下:

template<typename _Tp>struct __strip_reference_wrapper<reference_wrapper<_Tp> >{typedef _Tp& __type;};template<typename _Tp>struct __decay_and_strip{typedef typename __strip_reference_wrapper<typename decay<_Tp>::type>::__type __type;};/// decaytemplate<typename _Tp>class decay{typedef typename remove_reference<_Tp>::type __remove_type;public:typedef typename __decay_selector<__remove_type>::__type type;};

  通过源码可以看出,__decay_and_strip 就是将模板参数的类型萃取出来,如果模板参数是引用类型,该函数也会将 引用类型去除掉。

1.4  tuple 使用

#include<iostream>
#include<tuple>int main()
{// 1. 直接构造 tuple 对象std::tuple<float, int, double, long, char, std::string>  tup(1.2, 33, 2.3, 66, 'a', "abcde");std::cout << std::get<0>(tup) << " ";std::cout << std::get<1>(tup) << " ";std::cout << std::get<2>(tup) << " ";std::cout << std::get<3>(tup) << " ";std::cout << std::get<4>(tup) << " ";std::cout << std::get<5>(tup) << std::endl;// 2. 利用 make_tuple 构造 tuple 对象std::tuple<float, int, double, long, char, std::string> tp = std::make_tuple(1.2, 33, 2.3, 66, 'a', "abcde");std::cout << std::get<0>(tp) << " ";std::cout << std::get<1>(tp) << " ";std::cout << std::get<2>(tp) << " ";std::cout << std::get<3>(tp) << " ";std::cout << std::get<4>(tp) << " ";std::cout << std::get<5>(tp) << std::endl;return 0;
}

输出:

三  实现简单的 tuple

// my_tuple.h
template<size_t _Idx, typename ...Args>
struct my_tuple_impl;template<size_t _Idx,typename _Head, typename ...Args>
struct my_tuple_impl<_Idx, _Head, Args...> : private my_tuple_impl<_Idx+1, Args...>
{typedef my_tuple_impl<_Idx+1,Args...> _inheritd;
public:my_tuple_impl(const _Head& head, const Args&... args):m_head(head),_inheritd(args...){}_Head&  head(){return m_head;}_inheritd& tail(){return *this;}private:_Head m_head;
};template<size_t _Idx,typename _Head>
struct my_tuple_impl< _Idx,_Head>
{
public:my_tuple_impl(const _Head& head):m_head(head){}const _Head&  head() const{return m_head;}private:_Head m_head;
};template< typename ... Args>
struct my_tuple : public my_tuple_impl<0, Args...>{typedef  my_tuple_impl<0, Args...>  impl;
public:my_tuple(const Args&... args):impl(args...){}};// main.cpp
#include<iostream>
#include"my_tuple.h"int main()
{my_tuple<float, int, double, long, char, std::string> tuple(1.2, 33, 2.3, 66, 'a', "abcde");std::cout << tuple.head() << " ";std::cout << tuple.tail().head() << " ";std::cout << tuple.tail().tail().head() << " ";std::cout << tuple.tail().tail().tail().head() << " ";std::cout << tuple.tail().tail().tail().tail().head() << " ";std::cout << tuple.tail().tail().tail().tail().tail().head() << " ";return 0;
}

输出:

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

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

相关文章

微信小程序封装vant 下拉框select 单选组件

先上效果图&#xff1a; 主要是用vant 小程序组件封装的&#xff1a;vant 小程序ui网址&#xff1a;vant-weapp 主要代码如下: 先封装子组件&#xff1a; select-popup 放在 components 文件夹里面 select-popup.wxml: <!--pages/select-popup/select-popup.wxml--> &…

爆肝整理,企业级性能测试-性能方案设计详细总结(二)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、测试策略 1&a…

【小沐学C++】C++ 实现鼠标键盘钩子HOOK

文章目录 1、简介2、相关函数2.1 SetWindowsHookEx2.2 UnhookWindowsHookEx2.3 CallNextHookEx 3、相关结构体3.1 KBDLLHOOKSTRUCT3.2 MSLLHOOKSTRUCT 4、挂钩过程5、代码测试5.1 代码1 结语 1、简介 https://learn.microsoft.com/zh-cn/windows/win32/winmsg/about-hooks 挂…

Java学习笔记(十)——异常

一、异常的概念 二、异常体系图&#xff08;重要&#xff09; 三、常见的异常 &#xff08;一&#xff09;常见的运行时异常 1、NullPointerException空指针异常 2、ArithmeticException数学运算异常 3、ArrayIndexOutOfBoundsException数组下标越界异常 4、ClassCastEx…

CSS 压重按钮 效果

<template><view class="cont"><div class="container"><div class="pane"><!-- 选项1 --><label class="label" @click="handleOptionClick(0)":style="{ color: selectedOption ==…

约数个数和约数之和算法总结

知识概览 约数个数 由算数基本定理 可得对于N的任何一个约数d&#xff0c;有 因为N的每一个约数和~的一种选法是一一对应的&#xff0c;根据乘法原理可得&#xff0c; 一个数的约数个数为 约数之和 一个数的约数之和公式为 多项式乘积的每一项为 正好对应的是一个数的每一个约…

【网络安全】upload靶场pass11-17思路

目录 Pass-11 Pass-12 Pass-13 Pass-14 Pass-15 Pass-16 Pass-17 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &#x…

JetCache源码解析——配置加载和初始化

JetCache自动化配置加载 JetCache的配置加载主要是在jetcache-autoconfigure模块中完成的&#xff0c;其中加载配置的核心类是JetCacheAutoConfiguration&#xff0c;主要是用于创建全局性的一些Bean&#xff0c;例如全局缓存配置类GlobalCacheConfig&#xff0c;AutoConfigur…

数据密集型应用系统设计--第2章 数据模型与查询语言

一、引言 数据模型可能是开发软件最重要的部分,而且还对如何思考待解决的问题都有深远的影响。 大多数应用程序是通过一层一层叠加数据模型来构建的。每一层都面临的关键问题是&#xff1a;如何将其用下一层来表示&#xff1f; 1.作为一名应用程序开发人员&#xff0c;观测现实…

yarn无法加载文件和‘vue-cli-service‘ 不是内部或外部命令解决方法

导致此错误的原因是&#xff0c;PowerShell 执行策略&#xff0c;默认设置为Restricted不加载配置文件或运行脚本。需变更设置为RemoteSigned&#xff0c;变更过程为&#xff1a; 1.运行 Windows PowerShell&#xff08;管理员&#xff09;&#xff0c;执行命令set-ExecutionPo…

使用redis时快速考虑的问题

使用场景 ap组件程序是否容忍极限丢失1s数据是否可以不依赖redis就能实现是否过度依赖redis 数据结构 5种结构选择不同结构有自己的限制&#xff0c;使用前需考虑限制考虑当前业务最适合那种解构&#xff0c;或多种解构混合使用 key设计 大keykey的格式热key敏感数据 过…

Redis的IO多路复用原理解析

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理、数据库技术&#x1f525;如果感觉博主的文章还不错的…

SpringCloud系列篇:入门讲解Spring Cloud是什么

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于SpringCloud的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.Spring Cloud是什么 二.Spring …

通俗易懂的15个Java Lambda表达式案例

文章目录 1. **实现Runnable接口**&#xff1a;2. **事件监听器**&#xff08;如Swing中的ActionListener&#xff09;&#xff1a;3. **集合遍历**&#xff08;使用forEach方法&#xff09;&#xff1a;4. **过滤集合**&#xff08;使用Stream API&#xff09;&#xff1a;5. …

硬盘结构损坏且无法读取恢复方法

硬盘结构损坏且无法读取是计算机存储设备的一种常见故障。当硬盘出现此类问题时&#xff0c;用户往往无法正常访问存储在硬盘中的数据。本文将深入分析硬盘结构损坏且无法读取的潜在原因&#xff0c;并探讨有效的解决方法&#xff0c;以帮助用户恢复数据和正常使用硬盘。 硬盘结…

Vue3 结合typescript 组合式函数(1)

在App.vue文件中 实现鼠标点击文件&#xff0c;显示坐标值 第一种方法 第二种方法&#xff1a;组合式函数 结果&#xff1a; 官网推荐组合函数&#xff1a;https://vueuse.org

进阶学习——Linux系统——程序和进程

目录 一、程序和进程的关系 1.程序 2.进程 2.1线程 2.2协程 3.进程与线程的区别 4.总结 4.1延伸 5.进程使用内存的问题 5.1内存泄漏——Memory Leak 5.2内存溢出——Memory Overflow 5.3内存不足——OOM&#xff08;out of memory&#xff09; 5.4进程使用内存出现…

vue3 里的 ts 类型工具函数

目录 前言一、PropType\<T>二、MaybeRef\<T>三、MaybeRefOrGetter\<T>四、ExtractPropTypes\<T>五、ExtractPublicPropTypes\<T>六、ComponentCustomProperties七、ComponentCustomOptions八、ComponentCustomProps九、CSSProperties 前言 相关 …

报告解读:中国新一代终端安全市场洞察,2023

报告解读 中国新一代终端安全市场洞察 2023 安全防御的“最前线” 01 混沌的企业安全 以下来自CSO们最关注的安全热点问题&#xff1a; Q1我们如何看待当下泛化的终端安全&#xff0c;混合的IT环境企业面临的安全变化&#xff1f; IDC&#xff1a;伴随着全球数字化转型的快…

山西电力市场日前价格预测【2024-01-08】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2024-01-08&#xff09;山西电力市场全天平均日前电价为247.82元/MWh。其中&#xff0c;最高日前电价为373.22元/MWh&#xff0c;预计出现在18:00。最低日前电价为0.00元/MWh&#xff0c;预计出…