Правильное чтение бинарного файла в вектор чисел

Как максимально правильно (без UB, без предположений о размере типов, без предположения о размере байта) заполнить вектор unsigned int-ов из бинарного файла, размер которого кратен 4, порядок байтов little endian?

Проблема с reinterpret_cast -- размер unsigned int может быть 8 байтов, тогда, насколько я понимаю, read будет читать файл кусочками по 8 байтов, а надо по 4 всё равно (т.е. для 12-байтового файла должен быть размер вектора 3, а не непонятно что). Правда ли это?

Проблемы с 1) читаем массив char*; 2) ходим с шагом 4 и суммируем байты со сдвигом -- надо в 2 раза больше памяти; char может быть знаковым, тогда битовые операции могут выдавать отрицательные значения. Можно ли как-то переиспользовать память буфера char*? Можно ли без UB reinterpret_cast его как unsigned char или сразу читать с беззнаковым типом?

Ещё видел варианты с std::istream_iterator в этом ответе. Выглядит красиво, но не уверен, что это правильно, из-за использования >>.


Ответы (2 шт):

Автор решения: Джонни Кэтсвилл

Просто считайте четыре байта из файла и битовыми операциями сконструируйте из них необходимое вам значение:

#include <climits>
#include <cstdint>

static_assert(CHAR_BIT == 8); //На всякий случай.

unsigned char buf[4];
fin.read(reinterpret_cast<char*>(&buf[0]), sizeof(buf));
std::uint_least32_t uint32_value =
    (static_cast<std::uint_least32_t>(buf[0]) << 0) + 
    (static_cast<std::uint_least32_t>(buf[1]) << 8) + 
    (static_cast<std::uint_least32_t>(buf[2]) << 16) + 
    (static_cast<std::uint_least32_t>(buf[3]) << 24);

Или считываем весь файл разом, без доп. буфера, затем обрабатываем байты на месте:

#include <vector>
#include <cstdint>
#include <climits>

std::size_t vect_size = ...;
std::vector<std::uint32_t> vect(vect_size, 0);
 
fin.read(reinterpret_cast<char*>(&vect[0]), vect.size() * sizeof(vect[0]));
if (sizeof(vect[0]) != 1)
    for (std::size_t i = 0; i < vect.size(); ++i)
    {
        auto val = vect[i];
        vect[i] = 0;
        for (std::size_t byte = 0; byte < sizeof(val); ++byte)
            vect[i] |= static_cast<std::uint32_t>( *(reinterpret_cast<unsigned char*>(&val) + byte) ) << CHAR_BIT * byte;
    }

Способ №3, учитывающий произвольное количество бит в байте, а также возможное наличие padding bits в типе.

#include <limits>
#include <array>

template <typename T>
constexpr int uchars_in_int()
{
    static_assert(std::numeric_limits<T>::is_integer);

    //Определяем количество бит в типе T, учавствующих в value representation
    constexpr int digits_in_int = 
        std::numeric_limits<T>::digits + std::numeric_limits<T>::is_signed;
    //Определяем количество объектов типа unsigned char, необходимых для 
    //представления одного объекта типа T
    constexpr int res = 
        digits_in_int / std::numeric_limits<unsigned char>::digits + 
        (digits_in_int % std::numeric_limits<unsigned char>::digits > 0);
    return res;
}

template <typename T>
std::array<unsigned char, uchars_in_int<T>()> int_to_uchars(T value)
{
    static_assert(std::numeric_limits<T>::is_integer);

    std::array<unsigned char, uchars_in_int<T>()> buf;

    if (buf.size() == 1)
        buf[0] = value;
    else
        for (unsigned i = 0; i < buf.size(); ++i)
        {
            buf[i] = value & static_cast<unsigned char>(-1);
            value >>= std::numeric_limits<unsigned char>::digits;
        }
    return buf;
}

template <typename T>
T uchars_to_int(std::array<unsigned char, uchars_in_int<T>()> buf)
{
    static_assert(std::numeric_limits<T>::is_integer);
    
    if (buf.size() == 1)
        return buf[0];

    T value = 0;    
    for (unsigned i = 0; i < buf.size(); ++i)
        value |= static_cast<T>(buf[i]) << (std::numeric_limits<unsigned char>::digits * i);
    return value;
}

В файл пишем так:

some_int_type value = ...;
auto buf = int_to_uchars(value);
fout.write(reinterpret_cast<char*>(&buf[0]), buf.size());

Читаем из файла так:

std::array<unsigned char, uchars_in_int<some_int_type>()> buf;
fin.read(reinterpret_cast<char*>(&buf[0]), buf.size());
auto value = uchars_to_int<some_int_type>(buf);

Для повышения производительности заводим в памяти буфер нужного размера, пишем байты в буфер, затем буфер в файл / считываем файл в буфер, восстанавливаем инты из буфера.


Хотелось бы увидеть пояснение от поставившего минус. Если я в чём не прав или такой код как у меня пишут только в первом классе, вы не стесняйтесь, выскажетесь :) Мне действительно интересно, как правильно сериализовать инты в бинарный файл максимально кроссплатформенно.

→ Ссылка
Автор решения: russo
std::vector<std::uint32_t> vec(size, 0);
 
fin.read(reinterpret_cast<char*>(&vec[0]), vec.size() * sizeof(vec[0]));
→ Ссылка