Безопасное использование `reinterpret_cast`

Если у меня есть совокупность целочисленных типов, и я хочу создать ее экземпляр со случайным образом, безопасно ли здесь использовать reinterpret_cast?

template <typename T>
auto random() -> T {
  static auto random_device = std::random_device{};
  static auto generator     = std::mt19937(random_device());
  static auto distribution  = std::uniform_int_distribution<std::uint8_t>{};

  auto bytes = std::array<std::uint8_t, sizeof(T)>{};

  for (auto i = std::size_t{0}; i < bytes.size(); ++i) {
    bytes[i] = distribution(generator);
  }

  return *reinterpret_cast<T *>(bytes.data());
}

Рассмотрите возможность использования std::bit_cast. Если T легко скопировать, то это безопасно.

Patrick Roberts 10.07.2023 08:34

Почему бы не написать специальную функцию для вашего агрегата, которая правильно устанавливает для всех членов или элементов случайное значение?

Some programmer dude 10.07.2023 08:36

Это небезопасно, строгое нарушение правил псевдонимов. Переработайте его в обратном приведении от T* к byte* или char*, что должно быть с static_cast.

273K 10.07.2023 08:36

@user207421 user207421 Почему вы убрали почтение?

273K 10.07.2023 08:39

Для C++23 есть std::start_lifetime_as

Pepijn Kramer 10.07.2023 08:39

Я, вероятно, должен упомянуть, что я использую GCC-10, поэтому bit_cast недоступен

Uy Hà 10.07.2023 08:53

@Someprogrammerdude Я могу, но это повторяется, поэтому я не хочу этого делать

Uy Hà 10.07.2023 08:58

@PepijnKramer GCC-10, так что для меня нет С++ 23

Uy Hà 10.07.2023 08:58

Большую часть повторения можно абстрагировать в классы или функции вместе с перегрузками. Но с другой стороны, у вас будет что-то безопасное, портативное и что нельзя будет использовать не по назначению (в вашем коде нет ничего, что мешало бы кому-то использовать random<std::string>(), что не даст очень хорошего результата).

Some programmer dude 10.07.2023 09:03
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
9
71
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Для универсального типа T это может быть небезопасно. Согласно справочнику c++, reinterpret_cast разрешен, если:

  1. Любой указатель объекта типа T1* может быть преобразован в другой объект. тип указателя cv T2*. Это точно эквивалентно static_cast<cv T2*>(static_cast<cv void*>(expression)) (из чего следует, что если T2 требование выравнивания не строже, чем у T1, значение указатель не меняется и преобразование полученного указателя обратно исходному типу дает исходное значение). В любом случае, Результирующий указатель может быть безопасно разыменован только в том случае, если это разрешено правила алиасинга типов (см. ниже).

Итак, вы можете сделать бросок от *std::uint8_t до *T. Но проблема в том, когда вы можете разыменовать результирующий указатель на T? Не для каждого типа T это разрешено. На той же странице сказано, что вы можете разыменовать AliasedType T, если:

AliasedType — это std::byte, (начиная с C++17) char или unsigned char: это позволяет исследовать объектное представление любого объекта как массив байтов.

Таким образом, если вы ограничите использование своей подпрограммы std::byte, char или unsigned char (как указано выше во второй цитате), да, вы можете безопасно разыменовать указатель в операторе return, и ваша функция должна быть безопасной.

Но вам лучше рассмотреть возможность использования std::byte для хранения общего буфера необработанной памяти.

to the types T mentioned above единственный тип, упомянутый выше, это std::uint8_t, верно? Вы также упоминаете std::byte, char and unsigned char. Итак, допустимы только std::uint8_t, std::byte, char и unsigned char T? Мне сложно пояснить "упомянутое выше".
KamilCuk 10.07.2023 08:45
you can do the cast from *std::uint8_t to *T ну да но which implies that if T2's alignment requirement is not stricter than T1's. Когда требования к выравниванию T2 более строгие, значение результирующего указателя не указывается. Я не думаю, что это так уж безопасно. eel.is/c++draft/expr#static.cast-14
KamilCuk 10.07.2023 08:49

@KamilCuk о выравнивании: конечно, это то, что я цитирую из справочника С++. Я думаю, что это не относится к простому преобразованию в std::byte, char или unsigned char. Это имело бы значение для другого псевдонима, подробно описанного в ссылке.

francesco 10.07.2023 08:55

По какой-то причине какой-то пользователь модифицировал мой пост и изменил его семантику, так и должно быть return *reinterpret_cast<T *>(bytes.data());

Uy Hà 10.07.2023 08:56

@UyHà Я изменил ответ в соответствии с типом возврата вашей функции.

francesco 10.07.2023 08:58
Ответ принят как подходящий

Firstly this will not compile in the general case. You cannot reinterpret_castinto an arbitrary type. IfT` не является типом указателя или целочисленным типом, который не будет компилироваться.

Чтобы исправить это, вы, вероятно, захотите вернуться

return *reinterpret_cast<T*>(bytes.data());

Но и этого не делайте. Если T не является ((un)signed) char или std::byte, это поведение undefined.

Правильный способ реализации такой функции — сначала убедиться, что целевой тип T удовлетворяет критериям для этого. Я ограничу это, потребовав, чтобы это было тривиально:

decltype(auto) get_mt19937() {
    static auto random_device = std::random_device{};
    static auto generator     = std::mt19937(random_device());
    return generator;
}

template <typename T>
auto random() -> T {

    static_assert(std::is_trivial_v<T>, "T must be trivial");

    // This is bad. It will instantiate a new mt19937 engine for each type `T`.
    //static auto random_device = std::random_device{};
    //static auto generator     = std::mt19937(random_device());
    
    auto& generator = get_mt19937();

    // no need to static
    auto distribution  = std::uniform_int_distribution<unsigned char>{};

    auto return_val = T{};
    // reinterpret_cast'ing to std::uint8_t* might not be legal
    auto* bytes = reinterpret_cast<unsigned char*>(&return_val);

    for (auto i = std::size_t{0}; i < sizeof(T); ++i) {
        bytes[i] = distribution(generator);
    }


    return return_val;
}

Мой пост был изменен кем-то, и его семантика изменилась, оператор возврата должен быть `return *reinterpret_cast<T *>(bytes.data());`

Uy Hà 10.07.2023 09:07

Другие вопросы по теме