Преглед изворни кода

Add abstract `Allocator` class

Benoit Blanchon пре 2 година
родитељ
комит
24aaab6e3e

+ 1 - 0
CHANGELOG.md

@@ -5,3 +5,4 @@ HEAD
 ----
 
 * Remove `StaticJsonDocument`
+* Add abstract `Allocator` class

+ 47 - 33
extras/tests/JsonDocument/BasicJsonDocument.cpp

@@ -8,37 +8,50 @@
 #include <sstream>
 #include <utility>
 
-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<SpyingAllocator> doc(4096, log); }
-    REQUIRE(log.str() == "A4096F");
+    { BasicJsonDocument doc(4096, &spyingAllocator); }
+    REQUIRE(spyingAllocator.log() == "A4096F");
   }
 
   SECTION("Copy construct") {
     {
-      BasicJsonDocument<SpyingAllocator> doc1(4096, log);
+      BasicJsonDocument doc1(4096, &spyingAllocator);
       doc1.set(std::string("The size of this string is 32!!"));
 
-      BasicJsonDocument<SpyingAllocator> doc2(doc1);
+      BasicJsonDocument doc2(doc1);
 
       REQUIRE(doc1.as<std::string>() == "The size of this string is 32!!");
       REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
       REQUIRE(doc2.capacity() == 4096);
     }
-    REQUIRE(log.str() == "A4096A4096FF");
+    REQUIRE(spyingAllocator.log() == "A4096A4096FF");
   }
 
   SECTION("Move construct") {
     {
-      BasicJsonDocument<SpyingAllocator> doc1(4096, log);
+      BasicJsonDocument doc1(4096, &spyingAllocator);
       doc1.set(std::string("The size of this string is 32!!"));
 
-      BasicJsonDocument<SpyingAllocator> doc2(std::move(doc1));
+      BasicJsonDocument doc2(std::move(doc1));
 
       REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
       REQUIRE(doc1.as<std::string>() == "null");
       REQUIRE(doc1.capacity() == 0);
       REQUIRE(doc2.capacity() == 4096);
     }
-    REQUIRE(log.str() == "A4096F");
+    REQUIRE(spyingAllocator.log() == "A4096F");
   }
 
   SECTION("Copy assign larger") {
     {
-      BasicJsonDocument<SpyingAllocator> doc1(4096, log);
+      BasicJsonDocument doc1(4096, &spyingAllocator);
       doc1.set(std::string("The size of this string is 32!!"));
-      BasicJsonDocument<SpyingAllocator> doc2(8, log);
+      BasicJsonDocument doc2(8, &spyingAllocator);
 
       doc2 = doc1;
 
@@ -96,14 +110,14 @@ TEST_CASE("BasicJsonDocument") {
       REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
       REQUIRE(doc2.capacity() == 4096);
     }
-    REQUIRE(log.str() == "A4096A8FA4096FF");
+    REQUIRE(spyingAllocator.log() == "A4096A8FA4096FF");
   }
 
   SECTION("Copy assign smaller") {
     {
-      BasicJsonDocument<SpyingAllocator> doc1(1024, log);
+      BasicJsonDocument doc1(1024, &spyingAllocator);
       doc1.set(std::string("The size of this string is 32!!"));
-      BasicJsonDocument<SpyingAllocator> doc2(4096, log);
+      BasicJsonDocument doc2(4096, &spyingAllocator);
 
       doc2 = doc1;
 
@@ -111,14 +125,14 @@ TEST_CASE("BasicJsonDocument") {
       REQUIRE(doc2.as<std::string>() == "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<SpyingAllocator> doc1(1024, log);
+      BasicJsonDocument doc1(1024, &spyingAllocator);
       doc1.set(std::string("The size of this string is 32!!"));
-      BasicJsonDocument<SpyingAllocator> doc2(1024, log);
+      BasicJsonDocument doc2(1024, &spyingAllocator);
 
       doc2 = doc1;
 
@@ -126,14 +140,14 @@ TEST_CASE("BasicJsonDocument") {
       REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
       REQUIRE(doc2.capacity() == 1024);
     }
-    REQUIRE(log.str() == "A1024A1024FF");
+    REQUIRE(spyingAllocator.log() == "A1024A1024FF");
   }
 
   SECTION("Move assign") {
     {
-      BasicJsonDocument<SpyingAllocator> doc1(4096, log);
+      BasicJsonDocument doc1(4096, &spyingAllocator);
       doc1.set(std::string("The size of this string is 32!!"));
-      BasicJsonDocument<SpyingAllocator> 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<ControllableAllocator> 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();
 

+ 8 - 8
extras/tests/JsonDocument/shrinkToFit.cpp

@@ -8,24 +8,25 @@
 #include <stdlib.h>  // malloc, free
 #include <string>
 
-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<ArmoredAllocator> 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);

+ 21 - 18
src/ArduinoJson/Document/BasicJsonDocument.hpp

@@ -5,58 +5,60 @@
 #pragma once
 
 #include <ArduinoJson/Document/JsonDocument.hpp>
+#include <ArduinoJson/Memory/Allocator.hpp>
+
+#include <stdlib.h>  // malloc, free
 
 ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
 
 // Helper to implement the "base-from-member" idiom
 // (we need to store the allocator before constructing JsonDocument)
-template <typename TAllocator>
 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 <typename TAllocator>
-class BasicJsonDocument : AllocatorOwner<TAllocator>, public JsonDocument {
+class BasicJsonDocument : AllocatorOwner, public JsonDocument {
  public:
-  explicit BasicJsonDocument(size_t capa, TAllocator alloc = TAllocator())
-      : AllocatorOwner<TAllocator>(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<TAllocator>(src), JsonDocument() {
+      : AllocatorOwner(src), JsonDocument() {
     copyAssignFrom(src);
   }
 
   // Move-constructor
-  BasicJsonDocument(BasicJsonDocument&& src) : AllocatorOwner<TAllocator>(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<TAllocator>, public JsonDocument {
                         detail::is_same<T, JsonArrayConst>::value ||
                         detail::is_same<T, JsonObject>::value ||
                         detail::is_same<T, JsonObjectConst>::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<TAllocator>, public JsonDocument {
     return true;
   }
 
-  using AllocatorOwner<TAllocator>::allocator;
+  using AllocatorOwner::allocator;
 
  private:
   detail::MemoryPool allocPool(size_t requiredSize) {

+ 1 - 18
src/ArduinoJson/Document/DynamicJsonDocument.hpp

@@ -6,27 +6,10 @@
 
 #include <ArduinoJson/Document/BasicJsonDocument.hpp>
 
-#include <stdlib.h>  // 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<DefaultAllocator> DynamicJsonDocument;
+typedef BasicJsonDocument DynamicJsonDocument;
 
 ARDUINOJSON_END_PUBLIC_NAMESPACE

+ 49 - 0
src/ArduinoJson/Memory/Allocator.hpp

@@ -0,0 +1,49 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2023, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Namespace.hpp>
+
+#include <stdlib.h>  // 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