Копировать тривиально копируемые типы, используя временные области хранения: это разрешено? - PullRequest
0 голосов
/ 05 февраля 2019

Этот вопрос является продолжением комментария к ответу другого вопроса .


Рассмотрим следующий пример:

#include <cstring>
#include <type_traits>
#include <cassert>

int main() {
    std::aligned_storage_t<sizeof(void*), alignof(void*)> storage, copy;

    int i = 42;
    std::memcpy(&storage, &i, sizeof(int));

    copy = storage;

    int j{};
    std::memcpy(&j, &copy, sizeof(int));

    assert(j == 42);
}

Это работает (для некоторых определений работает ).Однако стандарт сообщает нам следующее:

Для любого объекта (кроме подобъекта базового класса) типа тривиально копируемого типа T независимо от того, содержит ли объект допустимое значение типа T, лежащие в основе байты, составляющие объект, могут быть скопированы в массив char, unsigned char или std​::​byte.
Если содержимое этого массива копируется обратно в объект, объект должен впоследствии содержатьего первоначальная стоимость.[Пример:

#define N sizeof(T)
char buf[N];
T obj;                          // obj initialized to its original value
std::memcpy(buf, &obj, N);      // between these two calls to std​::​memcpy, obj might be modified
std::memcpy(&obj, buf, N);      // at this point, each subobject of obj of scalar type holds its original value

- конец примера]

И это:

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

T* t1p;
T* t2p;
    // provided that t2p points to an initialized object ...
std::memcpy(t1p, t2p, sizeof(T));
    // at this point, every subobject of trivially copyable type in *t1p contains
    // the same value as the corresponding subobject in *t2p

- конец примера]

В любом случае упоминается, что копируется тривиально копируемый тип в буфер, а затем копируется обратно в новый экземплярдопускается использование оригинального типа.
В приведенном выше примере я делаю нечто подобное, плюс копирую также буфер в новый буфер (это немного больше похоже на случай из реальной жизни).

В комментариях, связанных сВ верхней части вопроса автор говорит, что это поведение недостаточно указано .С другой стороны, я не вижу, например, как я могу отправить int по сети и использовать его на другом конце, если это не разрешено (скопировать int в буфер, отправить его по сети, получитьэто как буфер и memcpy это в экземпляре int - более или менее то, что я делаю в этом примере, без сети между ними.

Это разрешено некоторыми другими пулями стандарта, который я пропустилили это действительно недооценено ?

Ответы [ 2 ]

0 голосов
/ 05 февраля 2019

Мне это хорошо читается.

Вы скопировали базовые байты obj1 в obj2.Оба тривиальны и одного типа.Проза, которую вы цитируете, разрешает это в явном виде.

Тот факт, что указанные байты были временно сохранены в области хранения правильного размера и правильно выровнены через явно разрешенную реинтерпретацию как char*, не делает 'Кажется, это не изменится.Они все еще "те байты".Нет правила, которое гласит, что копирование должно быть «прямым», чтобы удовлетворять таким функциям.

Действительно, это не только полностью распространенный шаблон при работе с сетевым переносом (обычное использование, конечно, не делает этогосамо по себе), но это также исторически нормальная вещь, которая заключается в том, что стандарт будет сумасшедшим не учитывать (что дает мне всю необходимую мне уверенность в том, что оно действительно предназначено).

Я могу видеть, как могут возникнуть сомнения, учитывая, что сначала дается правило для копирования этих байтов обратно в исходный объект, а затем снова для копирования этих байтов в новый объект.Но я не могу обнаружить никакой логической разницы между этими двумя обстоятельствами, и поэтому нахожу, что первая цитируемая формулировка в значительной степени избыточна.Возможно, автор просто хотел быть предельно ясным, что эта безопасность применяется одинаково в обоих случаях.

0 голосов
/ 05 февраля 2019

Для меня это одна из самых неоднозначных проблем в C ++.Честно говоря, меня никогда не смущало что-либо в C ++ так сильно, как пробивка типов.Всегда есть угловой случай, который, кажется, не охвачен (или не указан, как вы это указали).

Однако преобразование из целых чисел в необработанную память (char*) должно быть разрешено для сериализации./ проверка базового объекта .

Какое решение?

Юнит-тесты .Это мое решение проблемы.Вы делаете то, что больше всего соответствует стандарту, и пишете базовые модульные тесты, которые проверяют ваше конкретное предположение.Затем, всякий раз, когда вы компилируете новую версию или переходите к новому компилятору, вы запускаете модульные тесты и проверяете, что компилятор делает то, что вы ожидаете.

...