std :: tuple中的空类

bol*_*lov 7 c++ tuples sizeof language-lawyer

即使类型是空类类型[...],任何对象或成员子对象的大小也必须至少为1,以便能够保证相同类型的不同对象的地址始终是不同的.

cppreference引用

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


编辑:阅读@ bolov的最后一份关于他的回答后,我仍然有一个问题:因为EmptyPOD它是安全的memcpy吧.但是如果你想记住一个"幽灵"地址(参见@ bolov的答案),你就会有效地写入一个int元素(sizoef(Empty)是1).这似乎不太好.

bol*_*lov 5

tl,dr这更加增加了我对库实现者的尊重。std::tuple一旦你开始思考如何实现这一点,他们为实现这种优化而必须遵循的规则是令人敬畏的。


自然地,我继续玩了一会儿,看看情况如何。

设置:

struct Empty {};

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

考试:

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;
}
Run Code Online (Sandbox Code Playgroud)

结果:

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)
Run Code Online (Sandbox Code Playgroud)

链接

我们从一开始就可以看到

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
Run Code Online (Sandbox Code Playgroud)

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

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

这让我想到:如果我们有Emptysizeof(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
Run Code Online (Sandbox Code Playgroud)

最后一个注意事项:可以为Empty元素设置“幻影”地址(它们似乎与元素“共享”内存int)。由于Empty是一个……好吧……空类,它没有非静态数据成员,这意味着无法访问为它们获得的内存。


Pet*_*ker 5

对象的大小必须大于零.子对象的大小没有该约束.这导致空基优化(EBO),其中空基类不占用空间(编译器在近20年前开始实现).反过来,这导致通常实现std::tuple作为继承链,其中空基类不占用空间.