Как мне заменить vector <uint8_t> :: const_iterator в API?

Мне дали задание полировать интерфейс библиотеки кодеков. Мы используем C++ 17, и я могу использовать только стандартную библиотеку (т.е. без Boost). В настоящее время существует класс Decoder который выглядит примерно так:

class Decoder : public Codec {

public:

    struct Result {
        vector<uint8_t>::const_iterator new_buffer_begin;
        optional<Metadata>              metadata;
        optional<Packet>                packet;
    };

    Result decode(vector<uint8_t>::const_iterator buffer_begin,
                  vector<uint8_t>::const_iterator buffer_end);

private:
    // irrelevant details
};

Вызывающий объект создает Decoder, а затем передает поток данных в декодер

  1. Чтение фрагмента данных из файла (но в будущем возможны другие источники) и добавление его в vector<uint8_t>.

  2. Вызов функции decode, передача итераторов для их вектора.

  3. Если возвращенный Result new_buffer_begin идентичен buffer_begin который был передан для decode, это означает, что в буфере недостаточно данных для декодирования чего-либо, и вызывающая сторона должна вернуться к шагу 1. В противном случае вызывающая сторона использует Metadata или Packet объект, который был декодирован, и возвращается к шагу 2, используя new_buffer_begin для следующего прохода.

Что мне не нравится в этом интерфейсе и что мне нужно улучшить:

  • Использование vector<uint8_t>::const_iterator кажется слишком конкретным. Есть ли более общий подход, который не заставляет абонента использовать vector? Я думал только об использовании интерфейса в стиле C; uint8_t * и длина. Есть ли альтернатива C++, которая является довольно общей?

  • Если данных было достаточно для декодирования чего-либо, значение будет иметь только metadata или packet. Я думаю, что std::variant или 2 обратных вызова (по одному для каждого типа) сделают этот код более самодокументированным. Я не уверен, что более идиоматично, хотя. Каковы плюсы и минусы каждого, и есть ли еще лучший подход?

Ответ 1

Я согласен с тем, что мандатный vector неуместен, и приветствую ваши попытки сделать интерфейс более полезным.

Если decode ожидает непрерывную последовательность uint8_t, проверенное (и наиболее гибкое) решение состоит в том, чтобы просто использовать const uint8_t* и std::size_t (или, альтернативно, два указателя, но указатель и длина более идиоматичны).

В С++ 20 вы можете сделать это с одним аргументом типа std::span<const uint8_t>. Или, возвращаясь к указателям, если вы действительно хотите использовать современные библиотечные инструменты ради них, вы можете запутать людей с помощью std::experimental::observer_ptr.

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

Ответ 2

В дополнение к @Justin действительное предложение пролетов:

  • Возможно, вы захотите использовать std::byte вместо uint8_t, поэтому:
    Result decode(std::span<const std::byte> buffer);
    
    или, если вы находитесь в C++ 17, используйте реализацию span из библиотеки поддержки руководств C++:
    #include <gsl/span>
    // etc.
    Result decode(gsl::span<const std::byte> buffer);
    
  • Если вы хотите поддерживать декодирование из контейнеров, отличных от необработанной памяти, используйте произвольные итераторы (в C++ 17 и ранее) или, возможно, диапазоны (в C++ 20). Версия итератора:

    template <typename InputIt>
    Result decode(InputIt start, InputIt end) { /* etc. */ }
    
  • Это подозрительно, что Decoder наследуется от Codec а не наоборот.

  • Вопрос о том, являются ли обратные вызовы хорошим выбором или нет, - это то, что трудно (для меня) ответить, не видя код. Но действительно используйте std::variant чтобы выразить тот факт, что у вас есть пакет или метаданные; Вы также можете "комбинировать" альтернативы, если вместо обратных вызовов вы используете варианты std::visit.

Ответ 3

С++ 20 будет иметь std::span, который делает то, что вы хотите:

    Result decode(std::span<uint8_t const> buffer);

std::span<T> семантически эквивалентен T* buffer, size_t size.


В С++ 17 есть некоторые реализации типа span которые эквивалентны std::span, такие как GSL gsl::span. См. Что такое "промежуток" и когда я должен его использовать? ,

Если вы не можете использовать какие-либо внешние библиотеки, подумайте над написанием собственного типа span, иначе uint8_t const* buffer_begin, uint8_t const* buffer_end могут работать.