小工具知识网

当前位置: 首页 > 行业知识 > C++之std::tuple(二) : 揭秘底层实现原理

行业知识

C++之std::tuple(二) : 揭秘底层实现原理

2026-10-24 21:00:17 来源:互联网转载

���关系列文章

C++之std::tuple(二) : 揭秘底层实现原理

C++三剑客之std::any(一) : 使用

C++之std::tuple(一) : 使用精讲(全)

C++三剑客之std::variant(一) : 使用

C++三剑客之std::variant(二):深入剖析

深入理解可变参数(va_list、std::initializer_list和可变参数模版)

std::apply源码分析

目录

1.std::tuple存储设计

2.std::tuple构造

3.std::tuple_size

4.std::get访问值

5.operator=

6._Equals

7.总结


之前的章节中讲解std::tuple的使用和一些注意事项,接下来我们就以vs2019的std::tuple的实现来讲解它的底层实现原理。

1.std::tuple存储设计

std::tuple存储的递归写法基于这样的思想:一个包含N(N>0)个元素的元组可以存储为一个元素(第1个元素,或者说是列表的头部)加上一个包含N-1个元素的元组(尾部),而包含0个元素的元组是单独的特殊情况。下面看一下示例:

std::tuple  a(true, 1, 3.0, "1112222");

因此,一个包含4个元素的元组a的递归构造如下:

1) 第一层递归。准备存储a的第一个元素true,剩下的是 std:tuple 对象。

2) 第二层递归。准备存储a的第二个元素1,剩下的是 std::tuple 对象。

3) 第三层递归。准备存储a第三个元素3.0, 剩下的是 std::tuple 对象。

4) 第四层递归。准备存储a第四个元素"1112222",剩余的是std::tuple,遇到std::tuple递归结束。此时a的才开始存储元素,并且存储元素是从"1112222"开始,然后递归开始返回。

5) 返回到第3步的递归,存储duble类型的3.0。

6) 返回到第2步的递归,存储第int类型的1。

7) 返回到第1步的递归,存储第bool类型的true。

也就是说,std::tuple 的构造函数中,最后一个传入的元素最先构造,最先传入的元素最后一个构造,符合递归顺序,即入栈顺序。下面从源码角度,具体来看看 std::tuple 的实现。

template class tuple : private tuple { // recursive tuple definitionpublic:    using _This_type = _This; //当前元素    using _Mybase    = tuple; //余下的元素    //以下都是构造函数,罗列了部分    template     constexpr tuple(_Tag, _This2&& _This_arg, _Rest2&&... _Rest_arg)        : _Mybase(_Exact_args_t{}, _STD forward(_Rest_arg)...), _Myfirst(_STD forward(_This_arg)) {}    template     constexpr tuple(_Tag, _Tpl&& _Right, index_sequence);    template     constexpr tuple(_Tag, _Tpl&& _Right)        : tuple(_Unpack_tuple_t{}, _STD forward(_Right),            make_index_sequence{}) {}    ...    //获取余下的元素类,不带const    constexpr _Mybase& _Get_rest() noexcept { // get reference to rest of elements        return *this;    }    //获取余下的元素类,带const    constexpr const _Mybase& _Get_rest() const noexcept { // get const reference to rest         of elements        return *this;    }    ...    //    template     friend constexpr tuple_element_t& get(tuple& _Tuple) noexcept;    template     friend constexpr const tuple_element_t& get(const tuple& _Tuple) noexcept;    template     friend constexpr tuple_element_t&& get(tuple&& _Tuple) noexcept;    template     friend constexpr const tuple_element_t&& get(const tuple&& _Tuple) noexcept;    template     friend constexpr auto&& _Tuple_get(tuple&& _Tuple) noexcept;    template     friend constexpr _Ty& get(tuple& _Tuple) noexcept;    template     friend constexpr const _Ty& get(const tuple& _Tuple) noexcept;    template     friend constexpr _Ty&& get(tuple&& _Tuple) noexcept;    template     friend constexpr const _Ty&& get(const tuple&& _Tuple) noexcept;        //包装的当前的元素类,    _Tuple_val _Myfirst; // the stored element    };

        从上面的代码看到,std::tuple的递归继承用到了private继承,说明各个元素都是独立的,互相没有关系。std::tuple的元素之间是一种组合的关系。另外一个是private继承可以造成empty base最优化,这对致力于“对象尺寸最小化”的程序开发者而言,可能很重要。

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

_Tuple_val _Myfirst; // the stored element

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

template struct _Tuple_val { // stores each value in a tuple    constexpr _Tuple_val() : _Val() {}    template     constexpr _Tuple_val(_Other&& _Arg) : _Val(_STD forward(_Arg)) {}    template  = 0>    constexpr _Tuple_val(const _Alloc&, allocator_arg_t, _Other&&... _Arg) : _Val(_STD forward(_Arg)...) {}    template     constexpr _Tuple_val(const _Alloc& _Al, allocator_arg_t, _Other&&... _Arg)        : _Val(allocator_arg, _Al, _STD forward(_Arg)...) {}    template     constexpr _Tuple_val(const _Alloc& _Al, allocator_arg_t, _Other&&... _Arg)        : _Val(_STD forward(_Arg)..., _Al) {}    _Ty _Val;};

在std::tuple类中定义_Myfirst的权限是public的,所以对外面而言是直接访问元素值的。

通过上面的分析,a中定义的类和类的继承关系图如下所示:

class std::tuple;class std::tuple;class std::tuple;class std::tuple;class std::tuple;

内存的分布图如下:

2.std::tuple构造

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

//分类构造template     constexpr tuple(_Tag, _This2&& _This_arg, _Rest2&&... _Rest_arg)        : _Mybase(_Exact_args_t{}, _STD forward(_Rest_arg)...), _Myfirst(_STD forward(_This_arg)) {}    template     constexpr tuple(_Tag, _Tpl&& _Right, index_sequence);    template     constexpr tuple(_Tag, _Tpl&& _Right)        : tuple(_Unpack_tuple_t{}, _STD forward(_Right),            make_index_sequence{}) {}    template     constexpr tuple(_Tag, const _Alloc& _Al, _This2&& _This_arg, _Rest2&&... _Rest_arg)        : _Mybase(_Alloc_exact_args_t{}, _Al, _STD forward(_Rest_arg)...),          _Myfirst(_Al, allocator_arg, _STD forward(_This_arg)) {}    template     constexpr tuple(_Tag, const _Alloc& _Al, _Tpl&& _Right, index_sequence);    template     constexpr tuple(_Tag, const _Alloc& _Al, _Tpl&& _Right)        : tuple(_Alloc_unpack_tuple_t{}, _Al, _STD forward(_Right),            make_index_sequence{}) {}//根据入口参数的不同分派到不同的构造函数template     constexpr explicit(_Tuple_conditional_explicit_v) tuple(_This2&& _This_arg,        _Rest2&&... _Rest_arg) noexcept(_Tuple_nothrow_constructible_v) // strengthened        : tuple(_Exact_args_t{}, _STD forward(_This_arg), _STD forward(_Rest_arg)...) {}tuple(const tuple&) = default;tuple(tuple&&)      = default;tuple& operator=(const volatile tuple&) = delete;

        它还提供了默认拷贝构造函数和移动构造函数(移动语义是C++11中新增的特性,可以参考C++之std::move(移动语义)-CSDN博客)。其实,它还有很多构造函数,写起来挺热闹,无非就是用不同的方式为它赋初值,故省略。

        上面的构造函数是根据_tag的不同被分派到不同的构造函数,它叫标签派发。源码中定义了如下的标签:

struct _Exact_args_t {    explicit _Exact_args_t() = default;}; // tag type to disambiguate construction (from one arg per element)struct _Unpack_tuple_t {    explicit _Unpack_tuple_t() = default;}; // tag type to disambiguate construction (from unpacking a tuple/pair)struct _Alloc_exact_args_t {    explicit _Alloc_exact_args_t() = default;}; // tag type to disambiguate construction (from an allocator and one arg per element)struct _Alloc_unpack_tuple_t {    explicit _Alloc_unpack_tuple_t() = default;}; // tag type to disambiguate construction (from an allocator and unpacking a tuple/pair)

3.std::tuple_size

先看一下源码:

template struct integral_constant {    static constexpr _Ty value = _Val;    using value_type = _Ty;    using type       = integral_constant;    constexpr operator value_type() const noexcept {        return value;    }    _NODISCARD constexpr value_type operator()() const noexcept {        return value;    }};// TUPLE INTERFACE TO tupletemplate struct tuple_size : integral_constant {}; // size of tupletemplate struct tuple_size : integral_constant {}; // size of pair

std::integral_constant 包装特定类型的静态常量。它是 C++ 类型特征的基类。

std::tuple_size利用sizeof...求得可变参数的个数。

4.std::get访问值

先看一下tuple_element,tuple_element是获取tuple的元素,包括_Myfirst和_MyBase,源码:

//tupletemplate struct _MSVC_KNOWN_SEMANTICS tuple_element { // enforce bounds checking    static_assert(_Always_false, "tuple index out of bounds");};//tuple的_index == 0template struct _MSVC_KNOWN_SEMANTICS tuple_element { // select first element    using type = _This;    // MSVC assumes the meaning of _Ttype; remove or rename, but do not change semantics    using _Ttype = tuple;};//tuple的_index > 0template struct _MSVC_KNOWN_SEMANTICS tuple_element    : tuple_element {}; // recursive tuple_element definition//pairtemplate struct _MSVC_KNOWN_SEMANTICS tuple_element {    static_assert(_Idx  

通过_Index获取tuple元素:

template _NODISCARD constexpr tuple_element_t& get(tuple& _Tuple) noexcept {    using _Ttype = typename tuple_element::_Ttype;    return static_cast(_Tuple)._Myfirst._Val;}template _NODISCARD constexpr const tuple_element_t& get(const tuple& _Tuple) noexcept {    using _Ttype = typename tuple_element::_Ttype;    return static_cast(_Tuple)._Myfirst._Val;}template _NODISCARD constexpr tuple_element_t&& get(tuple&& _Tuple) noexcept {    using _Ty    = tuple_element_t;    using _Ttype = typename tuple_element::_Ttype;    return static_cast(static_cast(_Tuple)._Myfirst._Val);}template _NODISCARD constexpr const tuple_element_t&& get(const tuple&& _Tuple) noexcept {    using _Ty    = tuple_element_t;    using _Ttype = typename tuple_element::_Ttype;    return static_cast(static_cast(_Tuple)._Myfirst._Val);}

上述代码分析get的流程:

1)通过_Index递归构造出类 tuple_element_t

2)   获取当前元素 _MyFirst.Val

通过_Ty获取tuple元素:

//辅助类template struct _Tuple_element {}; // backstop _Tuple_element definitiontemplate struct _Tuple_element { // select first element    static_assert(!_Is_any_of_v, "duplicate type T in get(tuple)");    using _Ttype = tuple;};template struct _Tuple_element { // recursive _Tuple_element definition    using _Ttype = typename _Tuple_element::_Ttype;};//通过_Ty  gettemplate _NODISCARD constexpr _Ty& get(tuple& _Tuple) noexcept {    using _Ttype = typename _Tuple_element::_Ttype;    return static_cast(_Tuple)._Myfirst._Val;}template _NODISCARD constexpr const _Ty& get(const tuple& _Tuple) noexcept {    using _Ttype = typename _Tuple_element::_Ttype;    return static_cast(_Tuple)._Myfirst._Val;}template _NODISCARD constexpr _Ty&& get(tuple&& _Tuple) noexcept {    using _Ttype = typename _Tuple_element::_Ttype;    return static_cast(static_cast(_Tuple)._Myfirst._Val);}template _NODISCARD constexpr const _Ty&& get(const tuple&& _Tuple) noexcept {    using _Ttype = typename _Tuple_element::_Ttype;    return static_cast(static_cast(_Tuple)._Myfirst._Val);}

上述代码分析get的流程:

1)通过_Ty递归构造出类 _Tuple_element

2)   获取当前元素 _MyFirst.Val

5.operator=

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

    tuple& operator=(const volatile tuple&) = delete;    //以下是特殊情况是可以赋值的    template     _CONSTEXPR20 tuple& operator=(_Identity_t _Right) noexcept(        conjunction_v) /* strengthened */ {        _Myfirst._Val = _Right._Myfirst._Val;        _Get_rest()   = _Right._Get_rest();        return *this;    }    template     _CONSTEXPR20 tuple& operator=(_Identity_t _Right) noexcept(        conjunction_v) {        _Myfirst._Val = _STD forward(_Right._Myfirst._Val);        _Get_rest()   = _STD forward(_Right._Get_rest());        return *this;    }    template     _CONSTEXPR20 tuple& operator=(const tuple& _Right) noexcept(        _Tuple_nothrow_assignable_v) /* strengthened */ {        _Myfirst._Val = _Right._Myfirst._Val;        _Get_rest()   = _Right._Get_rest();        return *this;    }    template     _CONSTEXPR20 tuple& operator=(tuple&& _Right) noexcept(        _Tuple_nothrow_assignable_v) /* strengthened */ {        _Myfirst._Val = _STD forward(_Right._Myfirst._Val);        _Get_rest()   = _STD forward(_Right._Get_rest());        return *this;    }    template     _CONSTEXPR20 tuple& operator=(const pair& _Right) noexcept(        _Tuple_nothrow_assignable_v) /* strengthened */ {        _Myfirst._Val             = _Right.first;        _Get_rest()._Myfirst._Val = _Right.second;        return *this;    }

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

6._Equals

    //tuple内置函数    template     constexpr bool _Equals(const tuple& _Right) const {        return _Myfirst._Val == _Right._Myfirst._Val && _Mybase::_Equals(_Right._Get_rest());    }    //重载operator==    template _NODISCARD constexpr bool operator==(const tuple& _Left, const tuple& _Right) {    static_assert(sizeof...(_Types1) == sizeof...(_Types2), "cannot compare tuples of different sizes");    return _Left._Equals(_Right);}

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

7.总结

终于写完了,也是想了好久才写的,涉及到的知识点主要是模版推导。写的不好的地方,欢迎批评指正;如果觉得好,点个赞,收藏关注!

上一篇:服务器阻止访问怎么设置?

下一篇:Win10游戏闪退终极解决方案,轻松应对闪退问题!