Предположим, у вас есть объект типа 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, независимо от того, имеет ли объект действительное значение typeT, базовые байты ([intro.memory]), составляющие объект может быть скопирован в массивcharилиunsigned char. Если содержимое массиваcharилиunsigned charкопируется обратно в объект, объект впоследствии сохраняет свою оригинальную стоимость. пример опущен
Затем возникает вопрос, является ли memcpy перезаписью экземпляра класса с возможностью копирования экземпляра "копирование" или "копирование"? Ответ на вопрос, по-видимому, определяет, является ли Meow_internal допустимым способом для компилятора реализовать тривиально-скопируемый класс Meow.
Если memcpy является "построением копии", то ответ заключается в том, что Meow_internal является допустимым, поскольку построение копии повторно использует память. Если memcpy является "присваиванием копии", то ответ заключается в том, что Meow_internal не является допустимой реализацией, поскольку присваивание не приводит к недействительности указателей на экземплярные элементы класса. Если memcpy - оба, я понятия не имею, что такое ответ.