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

一 可变参数模板 variadic template

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

cpp 复制代码
template<typename ...Args>
void print(Args... args);

template<typename ...Args>
class my_class;

1.1 可变参数模板函数

cpp 复制代码
#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 可变参数模板递归继承类

cpp 复制代码
// 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;
}

输出:

cpp 复制代码
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_map | hash 表 |

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

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

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

1.3 tuple 源码

cpp 复制代码
    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() { }

      explicit
      constexpr _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() { }

      explicit
      constexpr _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
cpp 复制代码
template<typename... _Elements>
class tuple : public _Tuple_impl<0, _Elements...>;
  • struct _Tuple_impl 有三个模板参数,_Idx 表示元组中当前元素的下标,从 0 开始,_Head 表示当前存储的元素类型, Tail 表示剩下的模板参数包。该类继承了两个基类,分别是 class _Tuple_impl(就算该类本身,但是其模板参数是不同的),该类不存储元素, _Head_base 是用来存储元素的。
cpp 复制代码
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 来实现元素的存储的
cpp 复制代码
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;
cpp 复制代码
    template<std::size_t _Idx, typename _Head>
    struct _Head_base<_Idx, _Head, false>;

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

cpp 复制代码
 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 有点奇怪,源码如下:

cpp 复制代码
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;
    };

/// decay
  template<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 使用

cpp 复制代码
#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

cpp 复制代码
// 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;
}

输出:

相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J5 天前
从“Hello World“ 开始 C++
c语言·c++·学习