Допустим, у меня есть API:
// api.h - Others can #include this header
#include <cstdint>
class A {
public:
// Write data into an opaque type.
// The user shouldn't directly access the underlying bits of this value.
// They should use this method instead.
void WriteVal(uint32_t data);
private:
uint64_t opaque_value_;
};
// api.cpp
#include <cstdlib>
#include "api.h"
namespace {
// This is the underlying struct that the opaque value represents.
struct alignas(8) Impl {
uint32_t x;
uint32_t y;
};
} // namespace
void A::WriteVal(uint32_t data) {
uint64_t *opaque_ptr = &opaque_value_;
Impl *ptr = reinterpret_cast<Impl *>(opaque_ptr);
memcpy(&ptr->y, &data, sizeof(data));
}
Есть ли какое-либо неопределенное поведение в методе A::WriteVal
?
Мое предположение было бы НЕТ по этим причинам:
- Повторная интерпретация приведения между
uint64_t *
иImpl *
сама по себе допустима, поскольку выравнивание типов указателей одинаково. - Существует только UB, если
ptr
нужно разыменовать явно, поскольку это нарушит строгие правила псевдонимов. memcpy
можно безопасно использовать вместо явного разыменования независимо от исходного типа указателя.
Верны ли мои рассуждения? Если это также считается UB, есть ли на С++ хороший способ записи в непрозрачный тип без недопустимых методов каламбура.
Моя цель — аккуратно выполнять операции над непрозрачным значением, которое под капотом представляет собой структуру, подробности о которой пользователи не должны знать.
Impl
? Почему бы просто неmemcpy
вreinterpret_cast<char*>(&opaque_value_) + sizeof(uint32_t)
? Кстати, вы явно разыменовываетеptr
в&ptr
, не так ли? - person Daniel Langr   schedule 27.04.2020Impl
может быть структурой с любым количеством произвольных полей для представления сложной логики. Идея этого заключается в том, что он чище и использует больше преимуществ системы типов, чтобы компилятор получал смещение члена через операторы доступа к члену, а не приведение кchar *
и получениеoffsetof
. Кроме того,&ptr->y
на самом деле не разыменовывает указатель. Это эквивалентно броску, а затем смещению, о котором вы упоминаете, но чище. Если вы также посмотрите на сборку для этого, вы увидите, чтоptr
на самом деле никогда не разыменовывается. - person Leo C Han   schedule 27.04.2020ptr
. Однако, как указал Сандер, у вас есть проблема с отступами (хотя маловероятно, что они могут быть междуx
иy
). Кроме того, я не уверен, гарантированно ли выравниваниеuint64_t
равно 8. Теоретически может быть и больше. Но вы можете статически утверждать, что и размер, и выравниваниеImpl
иuint64_t
одинаковы. - person Daniel Langr   schedule 27.04.2020x
иy
нет заполнения и чтоuint64_t
гарантированно составляет 8 байтов на цели, которую я использую, поэтомуstatic_assert(sizeof(Impl) == sizeof(uint64_t) && alignof(Impl) == alignof(uint64_t))
проходит. Это так, по крайней мере, на x86_64-linux-gnu. - person Leo C Han   schedule 27.04.2020