Как мне заменить 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
, а затем передает поток данных в декодер
-
Чтение фрагмента данных из файла (но в будущем возможны другие источники) и добавление его в vector<uint8_t>
.
-
Вызов функции decode
, передача итераторов для их вектора.
-
Если возвращенный 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
могут работать.