【问题标题】:How should I replace vector<uint8_t>::const_iterator in an API?我应该如何在 API 中替换 vector<uint8_t>::const_iterator?
【发布时间】:2019-09-04 07:43:34
【问题描述】:

我的任务是完善编解码器库的界面。我们使用的是 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&lt;uint8_t&gt;

  2. 调用decode 函数,传递其向量的迭代器。

  3. 如果返回的Resultnew_buffer_begin 与传递给decodebuffer_begin 相同,这意味着缓冲区中没有足够的数据来解码任何内容,调用者应该返回第 1 步。否则,调用者会使用已解码的 MetadataPacket 对象,并返回第 2 步,使用 new_buffer_begin 进行下一次传递。

我不喜欢这个界面并需要帮助改进的地方:

  • 使用vector&lt;uint8_t&gt;::const_iterator 似乎过于具体。有没有更通用的方法不强制调用者使用vector?我正在考虑只使用 C 风格的界面; uint8_t * 和长度。有没有相当通用的 C++ 替代方案?

  • 如果有足够的数据来解码某些东西,那么只有metadata packet 会有一个值。我认为std::variant 或 2 个回调(每种类型一个)将使此代码更具自我记录性。我不确定哪个更惯用。各有什么优缺点,有没有更好的方法?

【问题讨论】:

  • Is there a C++ alternative that's fairly generic? 模板。
  • typedef vector&lt;uint8_t&gt;::const_iterator it_t;using it_t= vector&lt;uint8_t&gt;::const_iterator; 会更干净。
  • 我喜欢回调方法,为产生的每种结果传递一个带有回调的消费者对象。当方法返回时,您保证最多调用一个回调。但是你也可以有一个异步变体。 API 可以通过向消费者添加更多回调来发展。 std::variant 也不错,但可能需要用户检查哪一个可用(实际上并没有从两个选项改变)。

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


【解决方案1】:

我同意强制使用 vector 是不恰当的,并赞赏您尝试使界面更有用的尝试。

如果decode 需要一个连续的uint8_t 序列,那么久经考验(也是最灵活)的解决方案就是采用const uint8_t*std::size_t(或者两个指针,但指针和长度更惯用)。

从 C++20 开始,您可以使用 std::span&lt;const uint8_t&gt; 类型的一个参数来执行此操作。或者回到指针,如果你真的想为此使用现代库工具,你可以用std::experimental::observer_ptr混淆人们。

您也可以考虑将decode 设为接受任何迭代器对的模板,并且(如果需要连续性)强制要求迭代器反映连续 序列,即使仅通过文档也是如此。但是,将所有内容都制作成模板并不总是您想要的,而且它并不总是有用的。

【讨论】:

  • 标准库中有很多很多算法都需要某种类型的连续序列(仅举一个例子:std::sort)。但据我所知,它们都没有使用 T* 和长度,它们都使用迭代器。看起来很容易出错。
  • @Voo: A T* is 一个迭代器...您传递的第二个迭代器是 raw_pointer + length - 另一个指针。
  • @einpoklum 每个 T* 都是一个迭代器,但不是每个迭代器都是一个 T*,这几乎是我的观点。
  • @Voo,你为什么认为std::sort 需要连续内存?它只需要随机访问迭代器。
  • @Voo,如果库不是仅标头(我怀疑编解码器库是)或封闭源代码,我看不出在公共 API 中使用模板的好方法。
【解决方案2】:

除了@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 而不是回调,您也可以“组合”替代方案。

【讨论】:

  • Decoder 和另一个类 Encoder 继承自 Codec,因为 Codec 提供了大部分维护状态和一堆共享逻辑。不幸的是,我不允许分享代码。只是界面。您使用std::visit 的建议看起来很有希望。谢谢!
  • @splicer:在这种情况下,请考虑重命名编码器或将某些功能放入命名空间。
  • +1 推荐span。如果您不允许使用库或 C++20,我什至建议您实现自己的(简单)跨度
  • @Arvid:贾斯汀打败了我,你应该 +1 他的回答。另外 - GSL 是仅标题,使用它真的没什么大不了的,如果它是多个文件的事实 - 总是有GSL-lite
【解决方案3】:

C++20 将有 std::span,它可以满足您的需求:

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

std::span&lt;T&gt; 在语义上等同于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 可以工作。

【讨论】:

  • 与其写我自己的span,你认为basic_string_view&lt;byte&gt; 会是一个很好的临时解决方案吗?
  • @splicer 我几乎建议basic_string_view&lt;byte&gt;,但这是非常有情境的。如果您可以保证输入缓冲区将以空值结尾,则它可以工作。如果做不到,那就很危险了。
  • basic_string_view&lt;byte&gt; 同时使用指针和大小进行实例化时,它允许将空字符存储在字符串中间。当然,在 API 中使用它仍然太容易出错。 但是,如果我使用指针和长度 API 样式,它在实现方面确实很有用。
猜你喜欢
  • 2013-09-01
  • 2015-09-15
  • 1970-01-01
  • 2019-02-05
  • 1970-01-01
  • 1970-01-01
  • 2020-12-09
  • 2021-07-11
  • 1970-01-01
相关资源
最近更新 更多