Google协议缓冲区和std::字符串用于任意二进制数据

Google protocol buffers and use of std::string for arbitrary binary data

本文关键字:用于 任意 二进制 数据 字符串 Google 缓冲区 std 协议      更新时间:2023-10-16

相关问题:向量<无符号字符>vs字符串表示二进制数据。

我的代码对任意二进制数据使用vector<unsigned char>。然而,我的许多代码都必须与谷歌的协议缓冲区代码接口。协议缓冲区对任意二进制数据使用std::string。这导致了很多丑陋的分配/复制/免费周期,只是为了在谷歌协议缓冲区和我的代码之间移动数据。在很多情况下,我需要两个构造函数(一个取向量,一个取字符串)或两个函数来将函数转换为二进制有线格式。

代码在内部大量处理原始结构,因为结构是内容可寻址的(通过哈希存储和检索)、签名的等等。所以这不仅仅是谷歌协议缓冲区的接口问题。对象在代码的其他部分也以原始形式进行处理。

我可以做的一件事就是把我所有的代码都剪下来,对任意的二进制数据使用std::string。我可以做的另一件事是尝试找出更有效的方法来将向量存储和检索到Google协议缓冲区对象中。我想我的另一个选择是创建标准的、简单但缓慢的字符串转换函数,并始终使用它们。这将避免猖獗的代码重复,但从性能的角度来看,这将是最糟糕的。

有什么建议吗?我错过了更好的选择吗?

这就是我试图避免的:

if(SomeCase)
{
    std::vector<unsigned char> rawObject(objectdata().size());
    memcpy(&rawObject.front(), objectdata().data(), objectdata().size());
    DoSometingWith(rawObject);
}

当原始数据已经存在时,分配、复制、处理、免费是完全没有意义的。

我知道并在使用中见过两种避免复制的方法。

传统的方法实际上是将指针/引用传递给已知实体。虽然这很好,而且没有什么大惊小怪的,但问题是它将你与给定的表示联系在一起,这需要在必要时进行转换(正如你所经历的那样)。

我发现LLVM的另一种方式:

  • ArrayRef
  • StringRef

这个想法非常简单:两者都有一个指向T数组开头的T*和一个指示元素数量的size_t

神奇的是,它们完全隐藏了实际的存储,无论是stringvector,还是动态或静态分配的C阵列。。。这并不重要。呈现的界面是完全统一的,不涉及复制。

唯一需要注意的是,它们确实拥有内存(Ref!)的所有权,因此如果您不小心,细微的错误可能会悄悄出现。尽管如此,如果您只在瞬态操作中使用它们(例如,在函数中),而不存储它们以备将来使用,通常是可以的。

我发现它们在缓冲区操作中非常方便,特别是由于免费的切片操作。范围比迭代器对更容易操作。


还有第三种方法,我已经体验过,但直到现在还从未在严肃的代码中使用过。其思想是vector<unsigned char>是一种非常低级的表示。通过提升抽象层并使用Buffer类,您可以完全封装内存的确切存储方式,这样就代码而言,它就不会成为问题。

然后,可以随意选择一个需要较少转换的内存表示。

为了避免此代码(您提供的),

if(SomeCase)
{
    std::vector<unsigned char> rawObject(objectdata().size());
    memcpy(&rawObject.front(), objectdata().data(), objectdata().size());
    DoSometingWith(rawObject);
}

假定objectDatastd::string,则考虑

typedef unsigned char      Byte;
typedef std::vector<Byte>  ByteVector;

然后例如

if( someCase )
{
    auto const& s = objectData;
    doSomethingWith( ByteVector( s.begin(), s.end() ) );
}