Cap'n Proto - 将st / :: Serialize结构化为std :: string以存储在LevelDB中

时间:2018-04-08 21:16:00

标签: c++ serialization leveldb capnproto

我想在LevelDB中存储一些Capnproto结构,所以我必须将它序列化为字符串,然后将其从std :: string反序列化。目前,我使用以下内容(改编自此处:https://groups.google.com/forum/#!msg/capnproto/viZXnQ5iN50/B-hSgZ1yLWUJ):

capnp::MallocMessageBuilder message;
WortData::Builder twort = message.initRoot<WortData>();
twort.setWid(1234);
twort.setW("Blabliblub");
kj::Array<capnp::word> dataArr = capnp::messageToFlatArray(message);
kj::ArrayPtr<kj::byte> bytes = dataArr.asBytes();
std::string data(bytes.begin(), bytes.end());
std::cout << data << std::endl;
const kj::ArrayPtr<const capnp::word> view(
    reinterpret_cast<const capnp::word*>(&(*std::begin(data))),
    reinterpret_cast<const capnp::word*>(&(*std::end(data))));
capnp::FlatArrayMessageReader message2(view);
WortData::Reader wortRestore = message2.getRoot<WortData>();
std::cout << wortRestore.getWid() << " " << std::string(wortRestore.getW()) << std::endl;

它基本上有效,但上面链接中的人不确定这种方法是否会在以后引起错误,而且由于讨论相当陈旧,我想问一下是否有更好的方法。 最后有人说“使用memcpy!”,但我不确定这是否有用,以及如何使用FlatArrayMessageReader所需的数组类型。

提前致谢!
dvs23

更新:

我尝试实现与字对齐相关的建议:

capnp::MallocMessageBuilder message;
WortData::Builder twort = message.initRoot<WortData>();
twort.setWid(1234);
twort.setW("Blabliblub");
kj::Array<capnp::word> dataArr = capnp::messageToFlatArray(message);
kj::ArrayPtr<kj::byte> bytes = dataArr.asBytes();
std::string data(bytes.begin(), bytes.end());
std::cout << data << std::endl;

if(reinterpret_cast<uintptr_t>(data.data()) % sizeof(void*) == 0) {
    const kj::ArrayPtr<const capnp::word> view(
        reinterpret_cast<const capnp::word*>(&(*std::begin(data))),
        reinterpret_cast<const capnp::word*>(&(*std::end(data))));
    capnp::FlatArrayMessageReader message2(view);
    WortData::Reader wortRestore = message2.getRoot<WortData>();
    std::cout << wortRestore.getWid() << " " << std::string(wortRestore.getW()) << std::endl;
}
else {
    size_t numWords = data.size() / sizeof(capnp::word);

    if(data.size() % sizeof(capnp::word) != 0) {
        numWords++;
        std::cout << "Something wrong here..." << std::endl;
    }

    std::cout << sizeof(capnp::word) << " " << numWords << " " << data.size() << std::endl;

    capnp::word dataWords[numWords];
    std::memcpy(dataWords, data.data(), data.size());
    kj::ArrayPtr<capnp::word> dataWordsPtr(dataWords, dataWords + numWords);
    capnp::FlatArrayMessageReader message2(dataWordsPtr);
    WortData::Reader wortRestore = message2.getRoot<WortData>();
    std::cout << wortRestore.getWid() << " " << std::string(wortRestore.getW()) << std::endl;
}

1 个答案:

答案 0 :(得分:1)

据我所知,链接的对话仍然准确无误。 (该主题上的大多数消息都是我,我是Cap'n Proto的作者......)

支持任何std::string的缓冲区很可能在实践中是字对齐的 - 但不能保证。从std::string读取时,您应该检查指针是否对齐(例如,通过reinterpret_cast<uintptr_t>(str.data()) % sizeof(void*) == 0)。如果对齐,您可以reinterpret_cast指向capnp::word*的指针。如果没有对齐,您需要复制。实际上,代码可能永远不会复制,因为std::string的后备缓冲区可能始终是对齐的。

在写作结束时,避免复制是比较棘手的。你编写的代码实际上是两份副本。

一个人在这里:

kj::Array<capnp::word> dataArr = capnp::messageToFlatArray(message);

一个人在这里:

std::string data(bytes.begin(), bytes.end());

看起来LevelDB支持一种名为Slice的类型,在编写时可以使用它而不是std::string,以避免第二个副本:

leveldb::Slice data(bytes.begin(), bytes.size());

这将引用底层字节而不是复制,并且应该可以在所有LevelDB写入函数中使用。

不幸的是,这里一个副本是不可避免的,因为LevelDB希望该值是一个连续的字节数组,而Cap'n Proto消息可以分成多个段。避免这种情况的唯一方法是让LevelDB添加对“收集写入”的支持。