私は、コーデックライブラリのインターフェイスを洗練するタスクを与えられました。私たちは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
がdecode
に渡されたbuffer_begin
と同一である場合、何もデコードするのに十分なデータがバッファになかったことを意味します。呼び出し元は手順1に戻る必要があります。それ以外の場合、呼び出し元はデコードされたMetadata
またはPacket
オブジェクトを使用し、次のパスにnew_buffer_begin
を使用して手順2に戻ります。 。
このインターフェースが嫌いで改善の助けが必要なもの:
vector<uint8_t>::const_iterator
の使用は過度に具体的です。呼び出し側にvector
の使用を強制しないより一般的なアプローチはありますか? Cスタイルのインターフェースだけを使用することを検討していました。 uint8_t *
と長さ。かなり一般的なC++の代替案はありますか?
何かをデコードするのに十分なデータがある場合、metadata
orpacket
のみが値を持ちます。 std::variant
または2つのコールバック(タイプごとに1つ)を使用すると、このコードがより自己文書化されると思います。どちらがもっと慣用的かはわかりませんが。それぞれの長所と短所は何ですか?さらに優れたアプローチはありますか?
vector
の強制は不適切であることに同意し、インターフェースをより有用にするためのあなたの試みを称賛します。
decode
がuint8_t
の連続したシーケンスを期待している場合、実証済みの(そして最も柔軟な)ソリューションは、const uint8_t*
とstd::size_t
(または代わりに2つのポインター、ただしポインターと長さはより慣用的です)。
C++ 20以降では、タイプ std::span<const uint8_t>
の1つの引数でこれを行うことができます。または、ポインタに戻って、もしそれのために最新のライブラリツールを本当に使いたいのであれば、人々を std::experimental::observer_ptr
と混同することができます。
また、decode
をイテレータのペアを受け入れるテンプレートにすることを検討することもできます。また、ドキュメントによってのみ、イテレータがcontiguousシーケンス。しかし、すべてをテンプレートにすることは、常にあなたが望むものであるとは限らず、それが常に有用であるとは限りません。
スパン の@ジャスティンの有効な提案に加えて:
std::byte
の代わりに uint8_t
を使用することも検討してください。したがって、Result decode(std::span<const std::byte> buffer);
またはC++ 17を使用している場合は、 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
を使用する場合は、代替を「組み合わせる」こともできます。C++ 20には std::span
があり、必要な処理を実行します。
Result decode(std::span<uint8_t const> buffer);
std::span<T>
は、意味的にはT* buffer, size_t size
と同等です。
C++ 17では、 GSL'sstd::span
のように、gsl::span
と同等のspan
型の実装がいくつかあります。 「スパン」とは何か、いつ使用するべきか? を参照してください。
外部ライブラリを使用できない場合は、独自のspan
タイプを作成することを検討してください。そうでない場合は、uint8_t const* buffer_begin, uint8_t const* buffer_end
が機能します。