What is the no-undefined-behavior way of deseriali

2020-08-17 05:14发布

To overcome alignment issues, I need to memcpy into a temporary. What type should that temporary be? gcc complains that the following reinterpret_cast will break strict aliasing rules:

template <typename T>
T deserialize(char *ptr) {
    static_assert(std::is_trivially_copyable<T>::value, "must be trivially copyable");
    alignas(T) char raw[sizeof(T)];
    memcpy(raw, ptr, sizeof(T));
    return *reinterpret_cast<T *>(raw);
}

(e.g. when T is "long").

I don't want to define a T, since I don't want to construct a T before overwriting it.

In a union, doesn't writing one member then reading another count as undefined behavior?

template<typename T>
T deserialize(char *ptr) {
    union {
        char arr[sizeof(T)];
        T obj;
    } u;

    memcpy(u.arr, ptr, sizeof(T));   // Write to u.arr
    return u.obj;   // Read from u.obj, even though arr is the active member.
}

2条回答
2楼-- · 2020-08-17 05:59

What you want is this:

T result;
char * p = reinterpret_cast<char *>(&result);   // or std::addressof(result) !

std::memcpy(p, ptr, sizeof(T));                 // or std::copy!!

return result;

No aliasing violation. If you want a T, you need to have a T. If your type is trivially copyable, then hopefully it is also trivially constructible and there is no cost. In any event, you have to copy the return operand out into the function return value, and that copy is elided, so there's really no extra cost here.

查看更多
Juvenile、少年°
3楼-- · 2020-08-17 06:21

You want to use std::aligned_storage class template. It's been designed to handle this exact problem. Here's a sample solution with some SFINAE based upon your check in your question.

template<class T>
typename std::enable_if<std::is_trivially_copyable<T>::value, T>::type deserialize(const char *data) {
    typename std::aligned_storage<sizeof(T), alignof(T)>::type destination;
    std::memcpy(&destination, data, sizeof(T));
    return reinterpret_cast<T &>(destination);
}
查看更多
登录 后发表回答