From a360958be3f998b66e12a2d3db0ea17a05a37757 Mon Sep 17 00:00:00 2001 From: gregoire-astruc Date: Tue, 24 Feb 2015 13:14:46 +0100 Subject: [PATCH] Implementation of a buffer release strategy. * Tests for Release feature. * Check vector_downward.buf_ before passing to deallocator. * Assertions. * Shared test between unique_ptr and GetBufferPointer() * Unnecessary using directives. * Reallocate vector if released on clear operation. * Use allocator attribute. * Renamed `Release()` to `ReleaseBufferPointer()` * For consistency with `GetBufferPointer()` * Updated documentation for ReleaseBuffer. Change-Id: I108527778e56ae5127abf9e5b1be6b445ad75cb7 --- docs/source/CppUsage.md | 6 ++++++ include/flatbuffers/flatbuffers.h | 44 ++++++++++++++++++++++++++++++++++++--- tests/test.cpp | 25 ++++++++++++---------- 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/docs/source/CppUsage.md b/docs/source/CppUsage.md index 3bd7069..ef7272a 100755 --- a/docs/source/CppUsage.md +++ b/docs/source/CppUsage.md @@ -112,6 +112,12 @@ be compressed, or whatever you'd like to do with it. You can access the start of the buffer with `fbb.GetBufferPointer()`, and it's size from `fbb.GetSize()`. +Calling code may take ownership of the buffer with `fbb.ReleaseBufferPointer()`. +Should you do it, the `FlatBufferBuilder` will be in an invalid state, +and *must* be cleared before it can be used again. +However, it also means you are able to destroy the builder while keeping +the buffer in your application. + `samples/sample_binary.cpp` is a complete code sample similar to the code above, that also includes the reading code below. diff --git a/include/flatbuffers/flatbuffers.h b/include/flatbuffers/flatbuffers.h index d6b6e9a..148d43e 100644 --- a/include/flatbuffers/flatbuffers.h +++ b/include/flatbuffers/flatbuffers.h @@ -26,6 +26,8 @@ #include #include #include +#include +#include #if __cplusplus <= 199711L && \ (!defined(_MSC_VER) || _MSC_VER < 1600) && \ @@ -84,6 +86,10 @@ typedef uint16_t voffset_t; typedef uintmax_t largest_scalar_t; +// Pointer to relinquished memory. +typedef std::unique_ptr> + unique_ptr_t; + // Wrapper for uoffset_t to allow safe template specialization. template struct Offset { uoffset_t o; @@ -358,9 +364,33 @@ class vector_downward { assert((initial_size & (sizeof(largest_scalar_t) - 1)) == 0); } - ~vector_downward() { allocator_.deallocate(buf_); } + ~vector_downward() { + if (buf_) + allocator_.deallocate(buf_); + } + + void clear() { + if (buf_ == nullptr) + buf_ = allocator_.allocate(reserved_); + + cur_ = buf_ + reserved_; + } + + // Relinquish the pointer to the caller. + unique_ptr_t release() { + // Actually deallocate from the start of the allocated memory. + std::function deleter( + std::bind(&simple_allocator::deallocate, allocator_, buf_)); - void clear() { cur_ = buf_ + reserved_; } + // Point to the desired offset. + unique_ptr_t retval(data(), deleter); + + // Don't deallocate when this instance is destroyed. + buf_ = nullptr; + cur_ = nullptr; + + return retval; + } size_t growth_policy(size_t bytes) { return (bytes / 2) & ~(sizeof(largest_scalar_t) - 1); @@ -385,10 +415,14 @@ class vector_downward { } uoffset_t size() const { + assert(cur_ != nullptr && buf_ != nullptr); return static_cast(reserved_ - (cur_ - buf_)); } - uint8_t *data() const { return cur_; } + uint8_t *data() const { + assert(cur_ != nullptr); + return cur_; + } uint8_t *data_at(size_t offset) { return buf_ + reserved_ - offset; } @@ -463,6 +497,10 @@ class FlatBufferBuilder FLATBUFFERS_FINAL_CLASS { // Get the serialized buffer (after you call Finish()). uint8_t *GetBufferPointer() const { return buf_.data(); } + // Get the released pointer to the serialized buffer. + // Don't attempt to use this FlatBufferBuilder afterwards! + unique_ptr_t ReleaseBufferPointer() { return buf_.release(); } + void ForceDefaults(bool fd) { force_defaults_ = fd; } void Pad(size_t num_bytes) { buf_.fill(num_bytes); } diff --git a/tests/test.cpp b/tests/test.cpp index 6cc4422..ae86f77 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -60,7 +60,7 @@ uint32_t lcg_rand() { void lcg_reset() { lcg_seed = 48271; } // example of how to build up a serialized buffer algorithmically: -std::string CreateFlatBufferTest() { +flatbuffers::unique_ptr_t CreateFlatBufferTest(std::string &buffer) { flatbuffers::FlatBufferBuilder builder; auto vec = Vec3(1, 2, 3, 0, Color_Red, Test(10, 20)); @@ -119,24 +119,25 @@ std::string CreateFlatBufferTest() { #endif // return the buffer for the caller to use. - return std::string(reinterpret_cast(builder.GetBufferPointer()), - builder.GetSize()); + auto bufferpointer = + reinterpret_cast(builder.GetBufferPointer()); + buffer.assign(bufferpointer, bufferpointer + builder.GetSize()); + + return builder.ReleaseBufferPointer(); } // example of accessing a buffer loaded in memory: -void AccessFlatBufferTest(const std::string &flatbuf) { +void AccessFlatBufferTest(const uint8_t *flatbuf, const std::size_t length) { // First, verify the buffers integrity (optional) - flatbuffers::Verifier verifier( - reinterpret_cast(flatbuf.c_str()), - flatbuf.length()); + flatbuffers::Verifier verifier(flatbuf, length); TEST_EQ(VerifyMonsterBuffer(verifier), true); TEST_EQ(strcmp(MonsterIdentifier(), "MONS"), 0); - TEST_EQ(MonsterBufferHasIdentifier(flatbuf.c_str()), true); + TEST_EQ(MonsterBufferHasIdentifier(flatbuf), true); // Access the buffer from the root. - auto monster = GetMonster(flatbuf.c_str()); + auto monster = GetMonster(flatbuf); TEST_EQ(monster->hp(), 80); TEST_EQ(monster->mana(), 150); // default @@ -593,8 +594,10 @@ void UnicodeTest() { int main(int /*argc*/, const char * /*argv*/[]) { // Run our various test suites: - auto flatbuf = CreateFlatBufferTest(); - AccessFlatBufferTest(flatbuf); + std::string rawbuf; + auto flatbuf = CreateFlatBufferTest(rawbuf); + AccessFlatBufferTest(reinterpret_cast(rawbuf.c_str()), rawbuf.length()); + AccessFlatBufferTest(flatbuf.get(), rawbuf.length()); #ifndef __ANDROID__ // requires file access ParseAndGenerateTextTest(); -- 2.7.4