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