[英]Empty class in std::tuple
即使类型是空类类型[...],任何对象或成员子对象的大小必须至少为1,以便能够保证相同类型的不同对象的地址始终是不同的。
我知道这个。 我刚刚发现的是,某些库类型(如std::tuple
不会对包含的空类使用任何大小。 这是真的? 如果是的话,那怎么样?
编辑:在阅读@ bolov关于他的回答的最后一个注释后,我仍然有一个问题:因为Empty
是POD
所以memcpy
是安全的。 但是如果你想记住一个“幽灵”地址(参见@ bolov的答案),你会有效地写入一个int
元素( sizoef(Empty)
是1)。 这似乎不太好。
对象的大小必须大于零。 子对象的大小没有该约束。 这导致空基优化(EBO),其中空基类不占用空间(编译器在近20年前开始实现)。 反过来,这导致std::tuple
通常实现为继承链,其中空基类不占用空间。
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)
我们从一开始就能看出来
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
保留空间,但在某些情况下,为Empty
分配了1 byte
(其余是填充)。 当Empty
元素是最后一个元素时,它看起来像是0
。
仔细检查通过获得的地址get
我们可以看到,无法在两个Empty
的元组元素具有相同的地址(这是与上面的一行规则),即使(这些地址Empty
)似乎是内部 int
元素。 此外, Empty
元素的地址不在容器元组的内存位置之外。
这让我想到:如果我们有比sizeof(int)
更多的尾随Empty
。 这会增加元组的大小吗? 确实如此:
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
是一个...... well ...空类,它没有非静态数据成员,这意味着无法访问为它们获取的内存。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.