Предположим, у вас есть объект типа 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
- оба, я понятия не имею, что такое ответ.