Является memcpy конструкции или присваивания типа тривиально-копируемого типа?

Предположим, у вас есть объект типа 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];
};

Компилятор мог хранить x и y Meow внутри буфера на любой части buffer, возможно даже со случайным смещением внутри buffer, если они правильно выровнены и не перекрываются. Смещение x и y может даже варьироваться случайным образом с каждой конструкцией, если компилятор желает. (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

Однако, действительно ли компилятору разрешено реализовать класс с возможностью копирования? Выделение px должно быть только undefined, если срок службы a.x закончился. Есть это? Соответствующие части проекта стандарта N3797 не очень ясны по этому вопросу. Это раздел [basic.life]/1:

Время жизни объекта - это свойство времени выполнения объекта. считается, что объект имеет нетривиальную инициализацию, если он имеет класс или совокупный тип, и он или один из его членов инициализируется конструктор, отличный от тривиального конструктора по умолчанию. [ Заметка: инициализация тривиальным конструктором copy/move является нетривиальной инициализация. - end note] Срок жизни объекта типа Tначинается, когда:

  • получено хранилище с правильным выравниванием и размером для типа T и
  • Если объект имеет нетривиальную инициализацию, его инициализация завершена.

Время жизни объекта типа T заканчивается, когда:

  • Если T - это тип класса с нетривиальным деструктором ([class.dtor]), начинается вызов деструктора или
  • хранилище, которое объект занимает, повторно используется или освобождается.

И это [basic.types]/3:

Для любого объекта (кроме подобъекта базового класса) тривиально тип копирования T, независимо от того, имеет ли объект действительное значение type T, базовые байты ([intro.memory]), составляющие объект может быть скопирован в массив char или unsigned char. Если содержимое массива char или unsigned char копируется обратно в объект, объект впоследствии сохраняет свою оригинальную стоимость. пример опущен

Затем возникает вопрос, является ли memcpy перезаписью экземпляра класса с возможностью копирования экземпляра "копирование" или "копирование"? Ответ на вопрос, по-видимому, определяет, является ли Meow_internal допустимым способом для компилятора реализовать тривиально-скопируемый класс Meow.

Если memcpy является "построением копии", то ответ заключается в том, что Meow_internal является допустимым, поскольку построение копии повторно использует память. Если memcpy является "присваиванием копии", то ответ заключается в том, что Meow_internal не является допустимой реализацией, поскольку присваивание не приводит к недействительности указателей на экземплярные элементы класса. Если memcpy - оба, я понятия не имею, что такое ответ.

Ответ 1

Мне ясно, что использование std::memcpy не приводит ни к построению, ни к присвоению. Это не конструкция, так как никакой конструктор не будет вызван. И это не назначение, так как оператор присваивания не будет вызван. Учитывая, что тривиально копируемый объект имеет тривиальные деструкторы, (копировать/перемещать) конструкторы и (копировать/перемещать) операторы присваивания, точка довольно спорная.

Вы, похоже, указали & para; 2 из & sect. 3.9 [basic.types]. On & para; 3, он заявляет:

Для любого тривиально-скопируемого типа T, если два указателя на T указывают на различные объекты T obj1 и obj2, где ни obj1, ни obj2 не является подобъектом базового класса, если базовые байты (1.7), составляющие obj1, копируются в obj2, 41obj2 впоследствии будет иметь то же значение, что и obj1. [Пример:
  T* t1p;
  T* t2p;
         //при условии, что t2p указывает на инициализированный объект...
  std::memcpy(t1p, t2p, sizeof(T));
         //в этот момент каждый подобъект тривиально-скопируемого типа в *t1p содержит          //то же значение, что и соответствующий подобъект в *t2p
- конечный пример]
41) Используя, например, библиотечные функции (17.6.1.2) std::memcpy или std::memmove.

Очевидно, что стандартом, позволяющим использовать *t1p во всех отношениях *t2p, было бы.

Продолжение дальше & para; 4:

Объектное представление объекта типа T представляет собой последовательность N неподписанных char объектов, занятых объектом типа T, где N равно sizeof(T). Представление значения объекта - это набор битов, которые содержат значение типа T. Для тривиально копируемых типов представление значений представляет собой набор битов в представлении объекта, который определяет значение, которое является одним дискретным элементом определенного для реализации набора значений. 42
42) Предполагается, что модель памяти С++ совместима с моделью языка программирования ISO/IEC 9899.

Использование слова перед обеими определенными терминами подразумевает, что любой данный тип имеет только одно представление объекта, и данный объект имеет только один. Ваш гипотетический морфинг внутреннего типа не должен существовать. В сноске дается понять, что намерение состоит в том, чтобы тривиально копируемые типы имели макет памяти, совместимый с C. Ожидается, что даже объект с нестандартной компоновкой, скопировав его, все равно позволит использовать его.

Ответ 2

В этом же черновике вы также найдете следующий текст, непосредственно следуя приведенному вами тексту:

Для любого тривиально-скопируемого типа T, если два указателя на T указывают на различные объекты T obj1 и obj2, где ни obj1, ни obj2 не является подобъектом базового класса, если скопированы базовые байты (1.7), составляющие obj1в obj2, obj2 впоследствии будет иметь такое же значение, как obj1.

Обратите внимание, что это говорит об изменении значения obj2, а не об уничтожении объекта obj2 и создании на его месте нового объекта. Поскольку не объект, а только его значение изменяется, любые указатели или ссылки на его элементы должны оставаться в силе.