【发布时间】:2014-11-28 02:12:06
【问题描述】:
假设您有一个T 类型的对象和一个适当对齐的内存缓冲区alignas(T) unsigned char[sizeof(T)]。如果您使用std::memcpy 将T 类型的对象复制到unsigned char 数组中,这算作复制构造还是复制赋值?
如果一个类型是普通可复制但不是标准布局,那么可以想象这样的类:
struct Meow
{
int x;
protected: // different access-specifier means not standard-layout
int y;
};
可以这样实现,因为编译器不会被强制使用标准布局:
struct Meow_internal
{
private:
ptrdiff_t x_offset;
ptrdiff_t y_offset;
unsigned char buffer[sizeof(int) * 2 + ANY_CONSTANT];
};
编译器可以将 Meow 的 x 和 y 存储在 buffer 的任何部分的缓冲区中,甚至可能在 buffer 内的随机偏移处,只要它们正确对齐并且不重叠。如果编译器愿意,x 和 y 的偏移量甚至可以随每个构造随机变化。 (如果编译器愿意,x 可以在 y 之后使用,因为标准只要求相同访问说明符的成员按顺序排列,而 x 和 y 具有不同的访问说明符。)
这将满足可简单复制的要求; memcpy 将复制隐藏的偏移量字段,因此新副本将起作用。但有些事情是行不通的。例如,在 memcpy 上持有指向 x 的指针会中断:
Meow a;
a.x = 2;
a.y = 4;
int *px = &a.x;
Meow b;
b.x = 3;
b.y = 9;
std::memcpy(&a, &b, sizeof(a));
++*px; // kaboom
但是,真的允许编译器以这种方式实现一个可简单复制的类吗?仅当 a.x 的生命周期结束时,取消引用 px 才应该是未定义的行为。有吗? N3797 标准草案的相关部分在这个主题上不是很清楚。这是[basic.life]/1部分:
对象的生命周期是对象的运行时属性。一个 如果对象属于一个类,则称该对象具有非平凡的初始化 或聚合类型,并且它或其成员之一由 构造函数而不是普通的默认构造函数。 [ 注意: 由平凡的复制/移动构造函数初始化是不平凡的 初始化。 — 结束说明 ]
T类型对象的生命周期 开始时间:
- 获得了与
T类型正确对齐和大小的存储,并且- 如果对象有非平凡的初始化,它的初始化就完成了。
T类型对象的生命周期结束于:
- 如果
T是具有非平凡析构函数 ([class.dtor]) 的类类型,则析构函数调用开始,或者- 对象占用的存储空间被重用或释放。
这是[basic.types]/3:
对于平凡的任何对象(基类子对象除外) 可复制类型
T,对象是否拥有有效值 键入T,构成底层字节([intro.memory]) 对象可以复制到char或unsigned char的数组中。如果char或unsigned char数组的内容被复制回来 进入该对象,该对象随后应保持其原始 价值。 示例省略
那么问题就变成了,memcpy 是否覆盖了可简单复制的类实例“复制构造”或“复制分配”?问题的答案似乎决定了Meow_internal 是否是编译器实现可简单复制的类Meow 的有效方式。
如果memcpy 是“复制构造”,那么答案是Meow_internal 有效,因为复制构造正在重用内存。如果memcpy 是“复制赋值”,那么答案是Meow_internal 不是一个有效的实现,因为赋值不会使指向类的实例化成员的指针无效。如果memcpy 两者都是,我不知道答案是什么。
【问题讨论】:
-
如果你使用
memcpy那么它不是任何类型的构造或赋值。 -
既然你可以将
memcpy不是T的东西变成T- 这绝对算作存储的“重用”并结束了T对象的生命周期 - 我明白了没有理由为什么memcpy将T转换为T也不算“重用”。我同意@brianbeuning 的观点,即讨论一个没有理智的人会编写或使用的假设编译器的标准合规性是毫无意义的。 -
@T.C.我问这个问题的原因是,如果
Meow_internal是非法实现,则意味着标准对offsetof需要standard-layout 结构的限制没有技术基础。有可能正式证明可简单复制足以支持offsetof,并因此证明标准更改其定义是合理的。 -
@dyp 我怀疑它会破坏这一点。
px没有指向T类型的对象;它指向一个子对象,据我所知,不能保证当您重用对象的存储时,指向其子对象的指针仍然有效(当然,它也确实重用了*px的存储,但是没有保证这种重用也满足 [basic.life]/7) 中的其他要求。 -
标准中可能没有完全明确定义。考虑 UB 邮件列表中的 N3751 和 related discussion。
标签: c++ c++11 copy-constructor language-lawyer memcpy