假设您有一个 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];
};
编译器可以在 buffer
的任何部分将缓冲区中的 x
和 y
存储在缓冲区中,甚至可以在 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
但是,编译器是否真的允许以这种方式实现一个简单的可复制类?解除引用 px
应该只是未定义的行为,如果 a.x
's lifetime has ended. Has it? The relevant portions of the N3797 draft Standard aren't非常明确的主题 . 这是 [basic.life]/1 部分:
对象的生命周期是对象的运行时属性 . 如果一个对象属于类或聚合类型,并且它或其成员之一由除了普通默认构造函数之外的构造函数初始化,则称该对象具有非平凡的初始化 . [注意:通过简单的复制/移动构造函数进行初始化是非平凡的初始化 . - 结束注释]类型T的对象的生命周期开始于:获得具有适当对齐和类型T大小的存储,并且如果对象具有非平凡的初始化,则其初始化完成 . 类型为T的对象的生命周期结束时:如果T是具有非平凡析构函数([class.dtor])的类类型,则析构函数调用将启动,或者对象占用的存储将被重用或释放 .
这是 [basic.types]/3 :
对于普通可复制类型T的任何对象(基类子对象除外),无论对象是否包含类型T的有效值,组成对象的基础字节([intro.memory])都可以复制到char或unsigned char数组 . 如果将char或unsigned char数组的内容复制回对象,则该对象应随后保持其原始值 . 示例省略
那么问题就是 memcpy
覆盖了一个简单的可复制的类实例"copy construction"或"copy-assignment"?这个问题的答案似乎决定 Meow_internal
是否是编译器实现可复制类 Meow
的有效方法 .
如果 memcpy
是"copy construction",则答案是 Meow_internal
有效,因为复制构造正在重用内存 . 如果 memcpy
是"copy-assignment",则答案是 Meow_internal
不是有效的实现,因为赋值不会使指向实例化类的成员的指针无效 . 如果 memcpy
都是,我不知道答案是什么 .
2 回答
我很清楚,使用
std::memcpy
既不会导致构造也不会导致分配 . 它不是构造,因为不会调用构造函数 . 也不是赋值,因为不会调用赋值运算符 . 鉴于一个简单的可复制对象具有简单的析构函数,(复制/移动)构造函数和(复制/移动)赋值运算符,这一点相当没有实际意义 .你似乎引用了§3.9[basic.types]中的¶2 . 在¶3,它指出:
显然,该标准旨在允许
*t1p
以各种方式使用*t1p
.继续到¶4:
在两个定义的术语前面使用单词 the 意味着任何给定类型仅具有 one 对象表示,并且给定对象仅具有 one 值表示 . 您的假设变形内部类型不应存在 . 脚注清楚地表明,对于具有与C兼容的存储器布局,意图是对于易于复制的类型 . 期望即使是具有非标准布局的对象,复制它仍将允许其可用 .
在同一草案中,您还可以直接在您引用的文本后面找到以下文本:
请注意,这说明了
obj2
的值的更改,而不是关于销毁对象obj2
并在其位置创建新对象 . 由于不是对象,而只是其值已更改,因此对其成员的任何指针或引用都应保持有效 .