【问题标题】:Empty class in std::tuplestd::tuple 中的空类
【发布时间】:2016-08-19 12:48:17
【问题描述】:

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

cppreference quote

这我知道。我刚刚发现一些库类型,比如std::tuple 不使用任何大小来包含空类。这是真的?如果是的话,这样可以吗?


编辑:在阅读了@bolov 关于他的回答的最后说明后,我还有一个问题:因为EmptyPOD,所以memcpy 是安全的。但是,如果您将 memcpy 到“幻像”地址(请参阅@bolov 的答案),您将有效地写入 int 元素(sizoef(Empty) 为 1)。好像不行。

【问题讨论】:

  • 是的,我有人格分裂...不,我没有!..没有人问过你!..有人饿了吗?...不,我们正在做一些事情...实际上披萨听起来不错……

标签: c++ tuples language-lawyer sizeof


【解决方案1】:

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

【讨论】:

  • 这样说似乎很容易...... ;-) 另一个答案只是描述了症状,但最好解释一下原因。
  • 我认为不止于此。 sizeof(std::tuple<int, Empty, Empty, Empty, Empty>4sizeof(std::tuple<int, Empty, Empty, Empty, Empty, Empty>)8
  • @bolov - 从非空基派生的类不为空。 int 作为第一个元素消除了 EBO。
【解决方案2】:

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(其余为填充)。当Empty 元素是最后一个元素时,它看起来可能是0

仔细检查通过get 获得的地址,我们可以看到从来没有两个Empty 元组元素具有相同的地址(这符合上述规则),即使这些地址(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

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

【讨论】:

  • This increased my respect for the library implementors even more. 哪些?哪个图书馆产生了这些结果?
  • @underscore_d ideone,所以我猜是 gcc
  • 是的,ideone 上面写着Language: C++ 有一个工具提示说gcc 5.1,所以那将是libstdc++。这是我使用的,所以很高兴知道。 :D
  • 这是我观察到的。一个更有洞察力和/或语言层次的答案会很棒。
  • 仅供参考:clang/libc++ 的结果略有不同:std::tuple(4) std::tuple(1) std::tuple(4)(1)(1)(4) 0x7fff5fbff568 - 0x7fff5fbff570 (8)(4)(1)(1)( 4)
猜你喜欢
  • 2011-06-20
  • 2019-06-15
  • 1970-01-01
  • 1970-01-01
  • 2021-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多