如何在API中替换vector <uint8_t> :: const_iterator?

时间:2019-04-13 22:13:27

标签: c++ c++17 binary-data idiomatic

我被赋予了完善编解码器库接口的任务。我们使用的是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的{​​{1}}与传递给new_buffer_begin的{​​{1}}相同,则意味着缓冲区以解码任何内容,调用者应返回到步骤1。否则,调用者将使用已解码的buffer_begindecode对象,并使用Metadata返回到步骤2。下一次通过。

我对此界面不满意,需要改进:

  • 使用Packet似乎过于具体。有没有更通用的方法不强制调用者使用new_buffer_begin?我当时正在考虑仅使用C风格的界面; vector<uint8_t>::const_iterator和长度。有没有相当通用的C ++替代方法?

  • 如果有足够的数据来解码内容,则只有vector uint8_t *会有值。我认为metadata或2个回调(每种类型一个)将使此代码更具自说明性。我不确定哪个更惯用。每种都有什么利弊,还有更好的方法吗?

3 个答案:

答案 0 :(得分:18)

我同意强制vector是不合适的,并为您使界面更有用的努力表示赞赏。

如果decode期望uint8_t的连续序列,那么经过实践检验(最灵活)的解决方案就是采用const uint8_t*std::size_t(或另外两个指针,但指针和长度更常见。

在C ++ 20中,您可以使用一个std::span<const uint8_t>类型的参数来执行此操作。还是回到指针,如果您真的想为此使用现代的图书馆工具,可以将人们与std::experimental::observer_ptr混淆。

您也可以考虑将decode用作接受任何迭代器对的模板,并且(如果需要连续性的话)强制执行命令(即使仅凭文档即可),以使迭代器反映连续序列。但是,将所有内容制作成模板并不总是您想要的,也不总是有用。

答案 1 :(得分:16)

除了@Justin的spans的有效建议:

  • 您可能还想考虑使用std::byte而不是uint8_t,所以:
    Result decode(std::span<const std::byte> buffer);
    
    或者,如果您使用的是C ++ 17,请使用C++ Guidelines Support library中的span实现:
    #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而不是回调,也可以“组合”替代方案。

答案 2 :(得分:5)

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's gsl::span。参见What is a "span" and when should I use one?

如果您不能使用任何外部库,请考虑编写自己的span类型,否则 uint8_t const* buffer_begin, uint8_t const* buffer_end可以工作。