std::tuple的实现

发现一个在gdb中打印tuple信息的脚本,为了看懂,去了解了tuple的实现,发现tuple的实现很需要技巧。
主要参考 走近std::tuple,揭秘C++元组的底层实现原理

一个简单的实现

先写一个丐版的:

template <typename Head, typename... Tail>
struct tuple : public tuple<Tail...>
{
    Head val{Head()};
    tuple()=default;
    tuple(const Head &head, const Tail&... tail)
        : tuple<Tail...>(tail...), val(head){}
};
template <typename Head>
struct tuple<Head>
{
    Head val{Head()};
    tuple()=default;
    tuple(const Head &head) : val(head){}
};

可以这样tuple<int, int> t(1, 2);初始化了,但不能获取和修改元素的值。至少需要添加一个get方法才基本能用:

template <int Idx, typename Head, typename... Tail>
struct tuple_impl : public tuple_impl<Idx+1, Tail...>
{
    Head val{Head()};
    tuple_impl()=default;
    tuple_impl(const Head& head, const Tail&... tail)
        : tuple_impl<Idx+1, Tail...>(tail...), val(head){}
};
template <int Idx, typename Head>
struct tuple_impl<Idx, Head>
{
    Head val{Head()};
    tuple_impl()=default;
    tuple_impl(const Head &head) : val(head){}
};
template <typename... Elements>
struct tuple : public tuple_impl<0, Elements...>
{
    using Base = tuple_impl<0, Elements...>;
    using Base::Base;
};

template <int Idx, typename Tuple>
struct tuple_element;
template <int Idx, typename Head, typename... Tail>
struct tuple_element<Idx, tuple<Head, Tail...>> : tuple_element<Idx-1, tuple<Tail...>>{};
template <typename Head, typename... Tail>
struct tuple_element<0, tuple<Head, Tail...>>
{
    using type = Head;
};
template <int Idx, typename Head, typename... Tail>
Head& get_helper(tuple_impl<Idx, Head, Tail...> &t)
{
    return t.val;
}
template <int Idx, typename... Elements>
typename tuple_element<Idx, tuple<Elements...>>::type& get(tuple<Elements...>& t)
{
    return get_helper<Idx>(t);
}

为了支持get的整型模板参数,需要为tuple添加了一个整型模板参数,但tuple本身是不能有其他模板参数的,所以引入tuple_impl,让tuple再继承tuple_impl。
看get函数的实现。举个例子,tuple<int, float>的继承关系是:tuple<int, float> : tuple_impl<0, int, float> : tuple_impl<1, float>,各个tuple_impl都是tuple的基类,所以调用get_helper时参数是可以直接隐式转换的。然后还差get返回值的推导,用tuple_element来实现。
现在可以这样简单使用了:

tuple<int, std::string> t(1, "2");
get<1>(t) = "abc";
std::cout << get<0>(t) << std::endl;
std::cout << get<1>(t) << std::endl;

当然这个tuple还有很多问题,比如get方法不支持const tuple,但主要思路就是这样。

考虑一个问题

上面的实现中tuple的内存布局其实是把各个元素堆在一起,所以在int占4字节的机器上使用默认对齐方式tuple<int, int>的大小就是8。但考虑元素类型是空类的情况:

struct sta{};
int main(int argc, char const *argv[])
{
tuple<int, sta> t;
std::cout << sizeof(t) << std::endl;
return 0;
}

结果也是8,sta实际占了一个字节,但因为字节对齐size是8。但如果使用std::tuple,大小则是4,std::tuple没有为空类付出额外的存储空间,符合c++的语言哲学:坚持零开销,不为用不到的特性付出代价。就看看std::tuple是怎么做的吧。

先看一段代码(参考 编译器优化之 Empty Base Class Optimization):

struct BaseEmpty{};
struct DerivedEmpty : BaseEmpty{};
struct DerivedSameEmpty : BaseEmpty, DerivedEmpty{};

struct AnotherBaseEmpty{};
struct AnotherDerivedSameEmpty : AnotherBaseEmpty, DerivedEmpty{};

struct Derived : BaseEmpty
{
    int data;
};

sizeof(BaseEmpty) : 1
sizeof(DerivedEmpty) : 1
sizeof(DerivedSameEmpty) : 2
sizeof(AnotherDerivedSameEmpty) : 1
sizeof(Derived) : 4

DerivedSameEmpty需要给直接基类BaseEmpty和间接基类BaseEmpty不同的地址,所以它们各占1个字节,AnotherDerivedSameEmpty没有这个困扰,只需要1个字节就够了。Derived不需要为空基类付出存储空间也能区分基类和派生类的地址。

我们知道private继承也可以实现has-a的关系,同时对于元素类型是非final空基类的情况,还能节省一点空间,std::tuple的实现就是这么做的:
引入一个_Head_base类,用来存储每个元素,让tuple_impl私有继承_Head_base。
对于非final空类,让_Head_base继承这个类;其他情况,_Head_base拥有一个成员来存储元素的值。

按上面说的修改tuple之后,再看一下tuple<sta, sta>的继承关系:

tuple<sta, sta> : tuple_impl<0, sta, sta>
tuple_impl<0, sta, sta> : tuple_impl<1, sta>, _Head_base<sta>
tuple_impl<1, sta> :  _Head_base<sta>
_Head_base<sta> : sta

这时候就出现了AnotherDerivedSameEmpty的问题,为了区分两个不同路径的_Head_base<sta>,tuple需要2个字节。std::tuple为_Head_base添加了一个整形模板参数,这样两个_Head_base<sta>就变成了,_Head_base<0, sta>,_Head_base<1, sta>,tuple便只需要1个字节了。

std::tuple的实现基本如此。
不过,一般也不会把空类做为tuple的元素吧,整这么复杂。

最后

我把打印tuple信息的脚本 ptuple.py 做了修改放出来。
相关的GDB的Python API文档是 23.3.2.3 Values From Inferior23.3.2.4 Types In Python

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值