Cap'n Proto - де-/ сериализация структуры в / из std::string для хранения в LevelDB
Я хочу сохранить некоторую структуру Capnproto в LevelDB, поэтому я должен сериализовать ее в строку и затем десериализовать обратно из std:: string. В настоящее время я играю со следующим (адаптировано отсюда: https://groups.google.com/forum/):
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 ответ
Связанный разговор все еще точен, насколько мне известно. (Большинство сообщений в этой теме - это я, и я автор 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 поддержку "сбора записей".