std::tuple中的空类

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

cppreference quote

我知道这一点。我刚刚发现的是,一些库类型(如std::tuple)不使用包含的空类的任何大小。这是真的吗?如果是,那么这怎么能行呢?
编辑:在阅读了@bolov最终答案中的最后一条注释后,我仍有一个问题:由于EmptyPOD,因此对其进行memcpy是安全的。但是,如果您将memcpy到一个“幻影”地址(参见@bolov的答案),则实际上会写入int元素内(sizoef(Empty)为1)。这似乎不太好。

2
是的,我有分裂的人格...不,我没有!..没人问你!..有人饿吗?..不,我们正在忙着做某事...实际上,披萨听起来不错... - bolov
2个回答

6

对象的大小必须大于零。而子对象的大小则没有这个限制。这导致了空基类优化(EBO),其中一个空的基类不占用空间(编译器在近20年前开始实现这个功能)。因此,std::tuple通常被实现为继承链,其中空的基类不占用空间。


就像你这样表述的时候,它看起来很容易...;-) 另一个答案只是描述了症状,但更好的方法是解释原因。 - underscore_d
我认为不仅如此。sizeof(std::tuple<int, Empty, Empty, Empty, Empty>)4,但是 sizeof(std::tuple<int, Empty, Empty, Empty, Empty, Empty>)8 - bolov
1
@bolov - 从非空基类派生的类不是空的。将int作为第一个元素可以消除 EBO。 - Pete Becker

5

简而言之,这让我更加敬重图书馆实现者们。他们必须遵循的规则以实现对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链接

我们从一开始就可以看到,


(需要继续翻译的内容不明确,请提供更多上下文。)
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字节Empty(其余是填充)。 当Empty元素是最后一个元素时,它看起来可能是0
通过使用get获得的地址仔细检查,我们可以看到即使那些Empty的地址(在int元素内)似乎在内部,也不会有两个Empty元组元素具有相同的地址(与上述规则一致)。而且,Empty元素的任何地址都不在容器元组的内存位置之外。
这让我想到:如果我们有比sizeof(int)更多的末尾Empty,那会增加元组的sizeof吗? 确实会增加。
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元素共享内存)。由于Empty是一个……好吧……空类,它没有非静态数据成员,这意味着为它们获取的内存无法访问。


这让我更加尊重图书馆实现者了。哪些人?哪个库产生了这些结果? - underscore_d
@underscore_d ideone,所以我猜是gcc。 - bolov
是的,ideone 上面写着“语言:C++”,悬停时会显示“gcc 5.1”,所以那就是“libstdc++”。这正是我使用的,所以很好知道。:D - underscore_d
这是我所观察到的。更深入和/或语言层面的答案会更棒。 - bolov
注意:clang/libc++ 的结果略有不同:std::tuple<int>(4)std::tuple<Empty>(1)std::tuple<int, Empty, Empty(8)(4)(1)(1)std::tuple<Empty, Empty, int>(4)(1)(1)(4)0x7fff5fbff568 - 0x7fff5fbff570 (8)(4)(1)(1)(4) - Richard Hodges

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接