memcpy是一个简单的可复制类型的构造或作业?

Myr*_*ria 26 c++ copy-constructor memcpy language-lawyer c++11

假设您有一个类型对象T和一个适当对齐的内存缓冲区alignas(T) unsigned char[sizeof(T)].如果您使用std::memcpy从类型对象复制Tunsigned char数组,是否考虑复制构造或复制分配?

如果一个类型可以轻易复制而不是标准布局,那么可以想象一个类如下:

struct Meow
{
    int x;
protected: // different access-specifier means not standard-layout
    int y;
};
Run Code Online (Sandbox Code Playgroud)

可以像这样实现,因为编译器不会被强制使用标准布局:

struct Meow_internal
{
private:
    ptrdiff_t x_offset;
    ptrdiff_t y_offset;
    unsigned char buffer[sizeof(int) * 2 + ANY_CONSTANT];
};
Run Code Online (Sandbox Code Playgroud)

编译器可以存储xy喵的缓冲器内的任何部分buffer,甚至可能在随机内的偏移buffer,只要它们被适当地对准和不重叠.的偏移xy可即使编译愿与各施工随机变化.(如果编译器希望,x可以继续y使用,因为标准只要求相同访问说明符的成员按顺序排列,x并且y具有不同的访问说明符.)

这将符合可轻易复制的要求; a memcpy将复制隐藏的偏移字段,因此新副本将起作用.但有些事情是行不通的.例如,持有指向x跨越a 的指针memcpy会破坏:

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
Run Code Online (Sandbox Code Playgroud)

但是,编译器是否真的允许以这种方式实现一个简单的可复制类?px如果a.x生命周期结束,解除引用应该只是未定义的行为.有吗?N3797标准草案的相关部分在这个问题上并不十分清楚.这是[basic.life]/1节:

对象的生命周期是对象的运行时属性.如果一个对象属于类或聚合类型,并且它或其成员之一由除了普通默认构造函数之外的构造函数初始化,则称该对象具有非平凡的初始化.[ 注意: 通过简单的复制/移动构造函数进行初始化是非平凡的初始化.- 结尾注释 ]类型对象的生命周期T 从以下开始:

  • 获得具有适当对齐和类型大小的存储T,并且
  • 如果对象具有非平凡的初始化,则其初始化完成.

类型对象的生命周期在以下情况T结束:

  • 如果T是具有非平凡析构函数([class.dtor])的类类型,则析构函数调用将启动,或者
  • 对象占用的存储器被重用或释放.

这是[basic.types]/3:

对于具有普通可复制类型的任何对象(基类子对象除外)T,无论对象是否保持有效的类型值,组成对象T的基础字节([intro.memory])都可以复制到数组中charunsigned char.如果阵列的内容charunsigned char将被复制回对象,该对象随后应保持其原始值. 示例省略

那么问题是,是否memcpy覆盖了一个可复制的类实例"复制构造"或"复制分配"?这个问题的答案似乎决定Meow_internal了编译器是否有效地实现了可复制的类Meow.

如果memcpy是"复制构造",则答案Meow_internal是有效的,因为复制构造正在重用内存.如果memcpy是"复制赋值",则答案是Meow_internal不是有效的实现,因为赋值不会使指向实例化类的成员的指针无效.如果memcpy是两者,我不知道答案是什么.

jxh*_*jxh 6

我很清楚,使用std::memcpy结果既不构造也不分配.它不是构造,因为不会调用构造函数.也不是赋值,因为不会调用赋值运算符.鉴于一个简单的可复制对象具有简单的析构函数,(复制/移动)构造函数和(复制/移动)赋值运算符,这一点相当没有实际意义.

你好像引用了§3.9[basic.types]中的2.在3,它指出:

对于任何平凡复制的类型T,如果两个指针T指向不同T对象obj1obj2,其中既不obj1也不obj2是碱基-类子对象,如果构成底层字节(1.7)obj1被复制到obj2,41 obj2随后应保持相同的值obj1.[例如:
  T* t1p;
  T* t2p;
          // 提供 t2p 指向初始化对象的...
  std::memcpy(t1p, t2p, sizeof(T));
          // 此时,每个子文本中的每个子对象都 *t1p 包含
          // 与相应子对象相同的值 *t2p
- 最后一个例子]
41)通过使用,例如,库函数(17.6.1.2)std::memcpystd::memmove.

显然,旨在允许*t1p以各种方式使用的标准*t2p将是.

继续到4:

类型对象的对象表示是由类型T的对象占据的N个无符号char对象的序列T,其中N等于sizeof(T).对象的值表示是保存type值的位集T.对于简单的可复制类型,值表示是对象表示中的一组位,用于确定值,该值是实现定义的值集的一个离散元素.42
42)意图是C++的内存模型与ISO/IEC 9899编程语言C的内存模型兼容.

在两个定义的术语前面使用单词the意味着任何给定的类型只有一个对象表示,而给定的对象只有一个值表示.您的假设变形内部类型不应存在.脚注清楚地表明,对于具有与C兼容的存储器布局,意图是对于易于复制的类型.期望即使是具有非标准布局的对象,复制它仍将允许其可用.

  • `假设一个简单的可复制对象具有简单的析构函数,构造函数和赋值运算符.只需要复制和移动构造函数是微不足道的.平凡的可复制类型可以具有非平凡的"正常"构造函数.您可能正在考虑POD,它不能有构造函数,但它是一个可以轻易复制的更严格的超集. (2认同)