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

spl*_*cer 17 c++ idiomatic binary-data c++17

我被赋予了完善编解码器库接口的任务。我们使用的是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
};
Run Code Online (Sandbox Code Playgroud)

调用者实例化一个Decoder,然后通过以下方式将数据流馈送到解码器:

  1. 从文件中读取大量数据(但将来可能还会有其他来源),并将其附加到vector<uint8_t>

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

  3. 如果返回Resultnew_buffer_beginbuffer_begin传递给的相同,则decode意味着缓冲区中没有足够的数据来解码任何内容,并且调用方应返回到步骤1。否则,调用方将使用已解码的MetadataPacket对象。 ,然后返回到步骤2,new_buffer_begin用于下一遍。

我对这个介面不满意,需要改善的地方:

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

  • 如果有足够的数据来解码某些内容,则只有metadata packet将有一个值。我认为std::variant2个回调(每种类型一个)将使此代码更具自说明性。我不确定哪个更惯用。每种方法的优缺点是什么,是否有更好的方法?

Lig*_*ica 18

我同意强制性操作vector是不合适的,并称赞您为使界面更有用而进行的尝试。

如果decode期望使用的连续序列uint8_t,则经过测试(最灵活)的解决方案只是采用a const uint8_t*和a std::size_t(或两个指针,但是指针和长度更惯用)。

在C ++ 20中,您可以使用type一个参数来执行此操作std::span<const uint8_t>。或者回到指针,如果您真的想为此使用现代的库工具,则可以使人们感到困惑std::experimental::observer_ptr

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

  • @Voo,为什么您认为`std :: sort`期望连续的内存?它只需要随机访问迭代器。 (6认同)
  • @Voo:一个T *是迭代器...而您传递的第二个迭代器是raw_pointer + length`-另一个指针。 (3认同)
  • @Voo,如果库不是仅标头(我怀疑编解码器库是标头)还是封闭源,则看不到在公共API中使用模板的好方法。 (3认同)
  • 标准库中有很多算法期望某种类型的连续序列(仅举一个:std :: sort)。但是据我所知,它们都不使用T *和长度,它们都使用迭代器。似乎很容易出错。 (2认同)

ein*_*ica 15

除了@Justin的有效跨度建议之外:

  • 您可能还需要考虑使用std::byte而不是uint8_t,因此:
    Result decode(std::span<const std::byte> buffer);
    
    Run Code Online (Sandbox Code Playgroud) 或者,如果您使用的是C ++ 17,请使用C ++ Guidelines Support库中的span实现:
    #include <gsl/span>
    // etc.
    Result decode(gsl::span<const std::byte> buffer);
    
    Run Code Online (Sandbox Code Playgroud)
  • 如果要支持从原始内存以外的容器进行解码,请使用任意迭代器(在C ++ 17和更早版本中)或可能的范围(在C ++ 20中)。迭代器版本:

    template <typename InputIt>
    Result decode(InputIt start, InputIt end) { /* etc. */ }
    
    Run Code Online (Sandbox Code Playgroud)
  • Decoder从a 继承Codec而不是从其他途径继承,这很可疑。

  • 回调是否是一个好选择的问题(对我而言)很难在不看代码的情况下回答。但确实可以使用std::variant来表达您拥有数据包或元数据的事实;如果您使用variant'代替回调,您也可以“组合”替代方案std::visit

  • +1用于推荐`span`。如果您不被允许使用库或C ++ 20,我什至建议您实现自己的(简单)跨度 (2认同)
  • @Arvid:Justin击败了我,您应该+1他的答案。另外-GSL仅是标头,使用它确实没什么大不了的,如果事实是它是多个文件,则总是[GSL-lite](https://github.com/martinmoene/gsl-lite) 。 (2认同)

Jus*_*tin 5

C ++ 20将具有std::span您想要的功能:

    Result decode(std::span<uint8_t const> buffer);
Run Code Online (Sandbox Code Playgroud)

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可以使用。

  • 当同时使用指针和大小实例化`basic_string_view &lt;byte&gt;`时,它允许将空字符存储在字符串中间。当然,在API中使用它仍然容易出错。*但是*,如果我使用指针和长度API样式,它在实现方面确实很有用。 (3认同)