Как я должен заменить вектор:: const_iterator в API? - PullRequest
17 голосов
/ 14 апреля 2019

Мне дали задание отшлифовать интерфейс библиотеки кодеков. Мы используем 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 обратных вызова (по одному для каждого типа) сделают этот код более самодокументированным. Я не уверен, что более идиоматично, хотя. Каковы плюсы и минусы каждого, и есть ли еще лучший подход?

Ответы [ 3 ]

18 голосов
/ 14 апреля 2019

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

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

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

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

16 голосов
/ 14 апреля 2019

В дополнение к действительному предложению @ Джастина spans :

  • Возможно, вы захотите использовать std::byte вместо uint8_t, поэтому:
    Result decode(std::span<const std::byte> buffer);
    
    или, если вы находитесь в C ++ 17, используйте реализацию span из библиотеки C ++ Guidelines Support :
    #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.
5 голосов
/ 14 апреля 2019

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

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

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


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

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

...