From 24aaab6e3e95f6bfb7d00b7555bd30feeee5d843 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Mon, 20 Mar 2023 14:47:27 +0100 Subject: [PATCH] Add abstract `Allocator` class --- CHANGELOG.md | 1 + .../tests/JsonDocument/BasicJsonDocument.cpp | 80 +++++++++++-------- extras/tests/JsonDocument/shrinkToFit.cpp | 16 ++-- .../Document/BasicJsonDocument.hpp | 39 ++++----- .../Document/DynamicJsonDocument.hpp | 19 +---- src/ArduinoJson/Memory/Allocator.hpp | 49 ++++++++++++ 6 files changed, 127 insertions(+), 77 deletions(-) create mode 100644 src/ArduinoJson/Memory/Allocator.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 54ca19b2..d9c1f9f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,3 +5,4 @@ HEAD ---- * Remove `StaticJsonDocument` +* Add abstract `Allocator` class diff --git a/extras/tests/JsonDocument/BasicJsonDocument.cpp b/extras/tests/JsonDocument/BasicJsonDocument.cpp index 9f034f07..7711f648 100644 --- a/extras/tests/JsonDocument/BasicJsonDocument.cpp +++ b/extras/tests/JsonDocument/BasicJsonDocument.cpp @@ -8,37 +8,50 @@ #include #include -class SpyingAllocator { +class SpyingAllocator : public Allocator { public: - SpyingAllocator(const SpyingAllocator& src) : _log(src._log) {} - SpyingAllocator(std::ostream& log) : _log(log) {} - SpyingAllocator& operator=(const SpyingAllocator& src) = delete; + virtual ~SpyingAllocator() {} - void* allocate(size_t n) { + void* allocate(size_t n) override { _log << "A" << n; return malloc(n); } - void deallocate(void* p) { + + void deallocate(void* p) override { _log << "F"; free(p); } + void* reallocate(void* ptr, size_t n) override { + _log << "R" << n; + return realloc(ptr, n); + } + + std::string log() const { + return _log.str(); + } + private: - std::ostream& _log; + std::ostringstream _log; }; -class ControllableAllocator { +class ControllableAllocator : public Allocator { public: ControllableAllocator() : _enabled(true) {} + virtual ~ControllableAllocator() {} - void* allocate(size_t n) { + void* allocate(size_t n) override { return _enabled ? malloc(n) : 0; } - void deallocate(void* p) { + void deallocate(void* p) override { free(p); } + void* reallocate(void* ptr, size_t n) override { + return realloc(ptr, n); + } + void disable() { _enabled = false; } @@ -48,47 +61,48 @@ class ControllableAllocator { }; TEST_CASE("BasicJsonDocument") { - std::stringstream log; + SpyingAllocator spyingAllocator; + ControllableAllocator controllableAllocator; SECTION("Construct/Destruct") { - { BasicJsonDocument doc(4096, log); } - REQUIRE(log.str() == "A4096F"); + { BasicJsonDocument doc(4096, &spyingAllocator); } + REQUIRE(spyingAllocator.log() == "A4096F"); } SECTION("Copy construct") { { - BasicJsonDocument doc1(4096, log); + BasicJsonDocument doc1(4096, &spyingAllocator); doc1.set(std::string("The size of this string is 32!!")); - BasicJsonDocument doc2(doc1); + BasicJsonDocument doc2(doc1); REQUIRE(doc1.as() == "The size of this string is 32!!"); REQUIRE(doc2.as() == "The size of this string is 32!!"); REQUIRE(doc2.capacity() == 4096); } - REQUIRE(log.str() == "A4096A4096FF"); + REQUIRE(spyingAllocator.log() == "A4096A4096FF"); } SECTION("Move construct") { { - BasicJsonDocument doc1(4096, log); + BasicJsonDocument doc1(4096, &spyingAllocator); doc1.set(std::string("The size of this string is 32!!")); - BasicJsonDocument doc2(std::move(doc1)); + BasicJsonDocument doc2(std::move(doc1)); REQUIRE(doc2.as() == "The size of this string is 32!!"); REQUIRE(doc1.as() == "null"); REQUIRE(doc1.capacity() == 0); REQUIRE(doc2.capacity() == 4096); } - REQUIRE(log.str() == "A4096F"); + REQUIRE(spyingAllocator.log() == "A4096F"); } SECTION("Copy assign larger") { { - BasicJsonDocument doc1(4096, log); + BasicJsonDocument doc1(4096, &spyingAllocator); doc1.set(std::string("The size of this string is 32!!")); - BasicJsonDocument doc2(8, log); + BasicJsonDocument doc2(8, &spyingAllocator); doc2 = doc1; @@ -96,14 +110,14 @@ TEST_CASE("BasicJsonDocument") { REQUIRE(doc2.as() == "The size of this string is 32!!"); REQUIRE(doc2.capacity() == 4096); } - REQUIRE(log.str() == "A4096A8FA4096FF"); + REQUIRE(spyingAllocator.log() == "A4096A8FA4096FF"); } SECTION("Copy assign smaller") { { - BasicJsonDocument doc1(1024, log); + BasicJsonDocument doc1(1024, &spyingAllocator); doc1.set(std::string("The size of this string is 32!!")); - BasicJsonDocument doc2(4096, log); + BasicJsonDocument doc2(4096, &spyingAllocator); doc2 = doc1; @@ -111,14 +125,14 @@ TEST_CASE("BasicJsonDocument") { REQUIRE(doc2.as() == "The size of this string is 32!!"); REQUIRE(doc2.capacity() == 1024); } - REQUIRE(log.str() == "A1024A4096FA1024FF"); + REQUIRE(spyingAllocator.log() == "A1024A4096FA1024FF"); } SECTION("Copy assign same size") { { - BasicJsonDocument doc1(1024, log); + BasicJsonDocument doc1(1024, &spyingAllocator); doc1.set(std::string("The size of this string is 32!!")); - BasicJsonDocument doc2(1024, log); + BasicJsonDocument doc2(1024, &spyingAllocator); doc2 = doc1; @@ -126,14 +140,14 @@ TEST_CASE("BasicJsonDocument") { REQUIRE(doc2.as() == "The size of this string is 32!!"); REQUIRE(doc2.capacity() == 1024); } - REQUIRE(log.str() == "A1024A1024FF"); + REQUIRE(spyingAllocator.log() == "A1024A1024FF"); } SECTION("Move assign") { { - BasicJsonDocument doc1(4096, log); + BasicJsonDocument doc1(4096, &spyingAllocator); doc1.set(std::string("The size of this string is 32!!")); - BasicJsonDocument doc2(8, log); + BasicJsonDocument doc2(8, &spyingAllocator); doc2 = std::move(doc1); @@ -142,11 +156,11 @@ TEST_CASE("BasicJsonDocument") { REQUIRE(doc1.capacity() == 0); REQUIRE(doc2.capacity() == 4096); } - REQUIRE(log.str() == "A4096A8FF"); + REQUIRE(spyingAllocator.log() == "A4096A8FF"); } SECTION("garbageCollect()") { - BasicJsonDocument doc(4096); + BasicJsonDocument doc(4096, &controllableAllocator); SECTION("when allocation succeeds") { deserializeJson(doc, "{\"blanket\":1,\"dancing\":2}"); @@ -167,7 +181,7 @@ TEST_CASE("BasicJsonDocument") { REQUIRE(doc.capacity() == 4096); REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); doc.remove("blanket"); - doc.allocator().disable(); + controllableAllocator.disable(); bool result = doc.garbageCollect(); diff --git a/extras/tests/JsonDocument/shrinkToFit.cpp b/extras/tests/JsonDocument/shrinkToFit.cpp index cc769725..bc2cb674 100644 --- a/extras/tests/JsonDocument/shrinkToFit.cpp +++ b/extras/tests/JsonDocument/shrinkToFit.cpp @@ -8,24 +8,25 @@ #include // malloc, free #include -class ArmoredAllocator { +class ArmoredAllocator : public Allocator { public: ArmoredAllocator() : _ptr(0), _size(0) {} + virtual ~ArmoredAllocator() {} - void* allocate(size_t size) { + void* allocate(size_t size) override { _ptr = malloc(size); _size = size; return _ptr; } - void deallocate(void* ptr) { + void deallocate(void* ptr) override { REQUIRE(ptr == _ptr); free(ptr); _ptr = 0; _size = 0; } - void* reallocate(void* ptr, size_t new_size) { + void* reallocate(void* ptr, size_t new_size) override { REQUIRE(ptr == _ptr); // don't call realloc, instead alloc a new buffer and erase the old one // this way we make sure we support relocation @@ -42,9 +43,7 @@ class ArmoredAllocator { size_t _size; }; -typedef BasicJsonDocument ShrinkToFitTestDocument; - -void testShrinkToFit(ShrinkToFitTestDocument& doc, std::string expected_json, +void testShrinkToFit(DynamicJsonDocument& doc, std::string expected_json, size_t expected_size) { // test twice: shrinkToFit() should be idempotent for (int i = 0; i < 2; i++) { @@ -60,7 +59,8 @@ void testShrinkToFit(ShrinkToFitTestDocument& doc, std::string expected_json, } TEST_CASE("BasicJsonDocument::shrinkToFit()") { - ShrinkToFitTestDocument doc(4096); + ArmoredAllocator armoredAllocator; + DynamicJsonDocument doc(4096, &armoredAllocator); SECTION("null") { testShrinkToFit(doc, "null", 0); diff --git a/src/ArduinoJson/Document/BasicJsonDocument.hpp b/src/ArduinoJson/Document/BasicJsonDocument.hpp index 4e36273c..b87b687d 100644 --- a/src/ArduinoJson/Document/BasicJsonDocument.hpp +++ b/src/ArduinoJson/Document/BasicJsonDocument.hpp @@ -5,58 +5,60 @@ #pragma once #include +#include + +#include // malloc, free ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE // Helper to implement the "base-from-member" idiom // (we need to store the allocator before constructing JsonDocument) -template class AllocatorOwner { public: - AllocatorOwner() {} - AllocatorOwner(TAllocator a) : _allocator(a) {} + AllocatorOwner(Allocator* allocator) : _allocator(allocator) {} void* allocate(size_t size) { - return _allocator.allocate(size); + return _allocator->allocate(size); } void deallocate(void* ptr) { if (ptr) - _allocator.deallocate(ptr); + _allocator->deallocate(ptr); } void* reallocate(void* ptr, size_t new_size) { - return _allocator.reallocate(ptr, new_size); + return _allocator->reallocate(ptr, new_size); } - TAllocator& allocator() { + Allocator* allocator() { return _allocator; } private: - TAllocator _allocator; + Allocator* _allocator; }; // A JsonDocument that uses the provided allocator to allocate its memory pool. // https://arduinojson.org/v6/api/basicjsondocument/ -template -class BasicJsonDocument : AllocatorOwner, public JsonDocument { +class BasicJsonDocument : AllocatorOwner, public JsonDocument { public: - explicit BasicJsonDocument(size_t capa, TAllocator alloc = TAllocator()) - : AllocatorOwner(alloc), JsonDocument(allocPool(capa)) {} + explicit BasicJsonDocument( + size_t capa, Allocator* alloc = detail::DefaultAllocator::instance()) + : AllocatorOwner(alloc), JsonDocument(allocPool(capa)) {} // Copy-constructor BasicJsonDocument(const BasicJsonDocument& src) - : AllocatorOwner(src), JsonDocument() { + : AllocatorOwner(src), JsonDocument() { copyAssignFrom(src); } // Move-constructor - BasicJsonDocument(BasicJsonDocument&& src) : AllocatorOwner(src) { + BasicJsonDocument(BasicJsonDocument&& src) : AllocatorOwner(src) { moveAssignFrom(src); } - BasicJsonDocument(const JsonDocument& src) { + BasicJsonDocument(const JsonDocument& src) + : AllocatorOwner(detail::DefaultAllocator::instance()) { copyAssignFrom(src); } @@ -70,13 +72,14 @@ class BasicJsonDocument : AllocatorOwner, public JsonDocument { detail::is_same::value || detail::is_same::value || detail::is_same::value>::type* = 0) - : JsonDocument(allocPool(src.memoryUsage())) { + : BasicJsonDocument(src.memoryUsage()) { set(src); } // disambiguate BasicJsonDocument(JsonVariant src) - : JsonDocument(allocPool(src.memoryUsage())) { + : AllocatorOwner(detail::DefaultAllocator::instance()), + JsonDocument(allocPool(src.memoryUsage())) { set(src); } @@ -132,7 +135,7 @@ class BasicJsonDocument : AllocatorOwner, public JsonDocument { return true; } - using AllocatorOwner::allocator; + using AllocatorOwner::allocator; private: detail::MemoryPool allocPool(size_t requiredSize) { diff --git a/src/ArduinoJson/Document/DynamicJsonDocument.hpp b/src/ArduinoJson/Document/DynamicJsonDocument.hpp index 3c2dc3a6..550503fc 100644 --- a/src/ArduinoJson/Document/DynamicJsonDocument.hpp +++ b/src/ArduinoJson/Document/DynamicJsonDocument.hpp @@ -6,27 +6,10 @@ #include -#include // malloc, free - ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE -// The allocator of DynamicJsonDocument. -struct DefaultAllocator { - void* allocate(size_t size) { - return malloc(size); - } - - void deallocate(void* ptr) { - free(ptr); - } - - void* reallocate(void* ptr, size_t new_size) { - return realloc(ptr, new_size); - } -}; - // A JsonDocument with a memory pool in the heap. // https://arduinojson.org/v6/api/dynamicjsondocument/ -typedef BasicJsonDocument DynamicJsonDocument; +typedef BasicJsonDocument DynamicJsonDocument; ARDUINOJSON_END_PUBLIC_NAMESPACE diff --git a/src/ArduinoJson/Memory/Allocator.hpp b/src/ArduinoJson/Memory/Allocator.hpp new file mode 100644 index 00000000..8183cfd7 --- /dev/null +++ b/src/ArduinoJson/Memory/Allocator.hpp @@ -0,0 +1,49 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2023, Benoit BLANCHON +// MIT License + +#pragma once + +#include + +#include // malloc, free + +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE + +class Allocator { + public: + virtual void* allocate(size_t size) = 0; + virtual void deallocate(void* ptr) = 0; + virtual void* reallocate(void* ptr, size_t new_size) = 0; + + protected: + ~Allocator() = default; +}; + +namespace detail { +class DefaultAllocator : public Allocator { + public: + void* allocate(size_t size) override { + return malloc(size); + } + + void deallocate(void* ptr) override { + free(ptr); + } + + void* reallocate(void* ptr, size_t new_size) override { + return realloc(ptr, new_size); + } + + static Allocator* instance() { + static DefaultAllocator allocator; + return &allocator; + } + + private: + DefaultAllocator() = default; + ~DefaultAllocator() = default; +}; +} // namespace detail + +ARDUINOJSON_END_PUBLIC_NAMESPACE