std :: tuple中的空类

时间:2016-08-19 12:48:18

标签: c++ tuples language-lawyer sizeof

  

任何对象或成员子对象的大小至少需要   1即使类型是空类类型[...],为了能够   保证相同类型的不同对象的地址   永远是截然不同的。

<子> cppreference quote

我知道。我刚刚发现的是,某些库类型(如std::tuple)对包含的空类不使用任何大小。这是真的?如果是的话,那怎么样?

编辑:在阅读@ bolov关于答案的最终说明之后,我还有一个问题:由于EmptyPODmemcpy对它是安全的。但是如果你想记住一个“幽灵”地址(参见@ bolov的答案),你就可以有效地在int元素内写入(sizoef(Empty)为1)。这似乎不合适。

2 个答案:

答案 0 :(得分:5)

对象的大小必须大于零。 子对象的大小没有该约束。这导致空基优化(EBO),其中空基类不占用空间(编译器在近20年前开始实现)。反过来,这会导致$ for i in f[123]; do echo "$i:"; cut -d \ -f 8 "$i" |sort|uniq -c; done f1: 1 xfield8 2 yfield8 1 zfield8 f2: 1 xfield8 1 yfield8 2 zfield8 f3: 3 xfield8 2 yfield8 1 zfield8 作为继承链的通常实现,其中空基类不会占用空间。

答案 1 :(得分:3)

tl,dr 这进一步增强了我对图书馆实施者的尊重。他们必须导航以实现std::tuple的这种优化的规则,一旦你开始考虑如何实现它,就会令人鼓舞。

当然,我继续玩了一下,看看情况如何。

设置:

struct Empty {};

template <class T> auto print_mem(const T& obj)
{
    cout << &obj << " - " << (&obj + 1) << " (" << sizeof(obj) << ")" << endl;
}

测试:

int main() {
    std::tuple<int> t_i;
    std::tuple<Empty> t_e;
    std::tuple<int, Empty, Empty> t_iee;
    std::tuple<Empty, Empty, int> t_eei;
    std::tuple<int, Empty, Empty, int> t_ieei;

    cout << "std::tuple<int>" << endl;
    print_mem(t_i);
    cout << endl;

    cout << "std::tuple<Empty>" << endl;
    print_mem(t_e);
    cout << endl;

    cout << "std::tuple<int, Empty, Empty" << endl;
    print_mem(t_iee);
    print_mem(std::get<0>(t_iee));
    print_mem(std::get<1>(t_iee));
    print_mem(std::get<2>(t_iee));
    cout << endl;

    cout << "std::tuple<Empty, Empty, int>" << endl;
    print_mem(t_eei);
    print_mem(std::get<0>(t_eei));
    print_mem(std::get<1>(t_eei));
    print_mem(std::get<2>(t_eei));
    cout << endl;

    print_mem(t_ieei);
    print_mem(std::get<0>(t_ieei));
    print_mem(std::get<1>(t_ieei));
    print_mem(std::get<2>(t_ieei));
    print_mem(std::get<3>(t_ieei));
    cout << endl;
}

结果:

std::tuple<int>
0xff83ce64 - 0xff83ce68 (4)

std::tuple<Empty>
0xff83ce63 - 0xff83ce64 (1)

std::tuple<int, Empty, Empty
0xff83ce68 - 0xff83ce6c (4)
0xff83ce68 - 0xff83ce6c (4)
0xff83ce69 - 0xff83ce6a (1)
0xff83ce68 - 0xff83ce69 (1)

std::tuple<Empty, Empty, int>
0xff83ce6c - 0xff83ce74 (8)
0xff83ce70 - 0xff83ce71 (1)
0xff83ce6c - 0xff83ce6d (1)
0xff83ce6c - 0xff83ce70 (4)

std::tuple<int, Empty, Empty, int>
0xff83ce74 - 0xff83ce80 (12)
0xff83ce7c - 0xff83ce80 (4)
0xff83ce78 - 0xff83ce79 (1)
0xff83ce74 - 0xff83ce75 (1)
0xff83ce74 - 0xff83ce78 (4)

Ideone link

我们可以从一开始就看到

sizeof(std:tuple<Empty>)                   == 1 (because the tuple cannot be empty)
sizeof(std:tuple<int>)                     == 4
sizeof(std::tuple<int, Empty, Empty)       == 4
sizeof(std::tuple<Empty, Empty, int)       == 8
sizeof(std::tuple<int, int>)               == 8
sizeof(std::tuple<int, Empty, Empty, int>) == 12

我们可以看到有时确实没有为Empty保留空间,但在某些情况下1 byte被分配给Empty(其余是填充)。当0元素是最后一个元素时,它看起来可能是Empty

仔细检查通过get获得的地址,我们可以看到永远不会有两个Empty元组元素具有相同的地址(符合上述规则),即使这些地址({{ 1}})似乎是内部 Empty元素。此外,int元素的地址不在容器元组的内存位置之外。

这让我想到:如果我们有更多跟踪Empty而不是Empty,该怎么办?这会增加元组的大小吗?确实如此:

sizeof(int)

最后一点注意:可以为sizeof(std::tuple<int>) // 4 sizeof(std::tuple<int, Empty>) // 4 sizeof(std::tuple<int, Empty, Empty>) // 4 sizeof(std::tuple<int, Empty, Empty, Empty>) // 4 sizeof(std::tuple<int, Empty, Empty, Empty, Empty>) // 4 sizeof(std::tuple<int, Empty, Empty, Empty, Empty, Empty>) // 8 yep. Magic 元素设置“幻像”地址(它们似乎与Empty元素“共享”内存)。由于int是...... well ...空类,因此它没有非静态数据成员,这意味着无法访问为它们获取的内存。