Add abstract Allocator class

This commit is contained in:
Benoit Blanchon
2023-03-20 14:47:27 +01:00
parent 17a482a9b1
commit 24aaab6e3e
6 changed files with 127 additions and 77 deletions

View File

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

View File

@ -8,37 +8,50 @@
#include <sstream> #include <sstream>
#include <utility> #include <utility>
class SpyingAllocator { class SpyingAllocator : public Allocator {
public: public:
SpyingAllocator(const SpyingAllocator& src) : _log(src._log) {} virtual ~SpyingAllocator() {}
SpyingAllocator(std::ostream& log) : _log(log) {}
SpyingAllocator& operator=(const SpyingAllocator& src) = delete;
void* allocate(size_t n) { void* allocate(size_t n) override {
_log << "A" << n; _log << "A" << n;
return malloc(n); return malloc(n);
} }
void deallocate(void* p) {
void deallocate(void* p) override {
_log << "F"; _log << "F";
free(p); 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: private:
std::ostream& _log; std::ostringstream _log;
}; };
class ControllableAllocator { class ControllableAllocator : public Allocator {
public: public:
ControllableAllocator() : _enabled(true) {} ControllableAllocator() : _enabled(true) {}
virtual ~ControllableAllocator() {}
void* allocate(size_t n) { void* allocate(size_t n) override {
return _enabled ? malloc(n) : 0; return _enabled ? malloc(n) : 0;
} }
void deallocate(void* p) { void deallocate(void* p) override {
free(p); free(p);
} }
void* reallocate(void* ptr, size_t n) override {
return realloc(ptr, n);
}
void disable() { void disable() {
_enabled = false; _enabled = false;
} }
@ -48,47 +61,48 @@ class ControllableAllocator {
}; };
TEST_CASE("BasicJsonDocument") { TEST_CASE("BasicJsonDocument") {
std::stringstream log; SpyingAllocator spyingAllocator;
ControllableAllocator controllableAllocator;
SECTION("Construct/Destruct") { SECTION("Construct/Destruct") {
{ BasicJsonDocument<SpyingAllocator> doc(4096, log); } { BasicJsonDocument doc(4096, &spyingAllocator); }
REQUIRE(log.str() == "A4096F"); REQUIRE(spyingAllocator.log() == "A4096F");
} }
SECTION("Copy construct") { SECTION("Copy construct") {
{ {
BasicJsonDocument<SpyingAllocator> doc1(4096, log); BasicJsonDocument doc1(4096, &spyingAllocator);
doc1.set(std::string("The size of this string is 32!!")); 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(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.as<std::string>() == "The size of this string is 32!!");
REQUIRE(doc2.capacity() == 4096); REQUIRE(doc2.capacity() == 4096);
} }
REQUIRE(log.str() == "A4096A4096FF"); REQUIRE(spyingAllocator.log() == "A4096A4096FF");
} }
SECTION("Move construct") { SECTION("Move construct") {
{ {
BasicJsonDocument<SpyingAllocator> doc1(4096, log); BasicJsonDocument doc1(4096, &spyingAllocator);
doc1.set(std::string("The size of this string is 32!!")); 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(doc2.as<std::string>() == "The size of this string is 32!!");
REQUIRE(doc1.as<std::string>() == "null"); REQUIRE(doc1.as<std::string>() == "null");
REQUIRE(doc1.capacity() == 0); REQUIRE(doc1.capacity() == 0);
REQUIRE(doc2.capacity() == 4096); REQUIRE(doc2.capacity() == 4096);
} }
REQUIRE(log.str() == "A4096F"); REQUIRE(spyingAllocator.log() == "A4096F");
} }
SECTION("Copy assign larger") { SECTION("Copy assign larger") {
{ {
BasicJsonDocument<SpyingAllocator> doc1(4096, log); BasicJsonDocument doc1(4096, &spyingAllocator);
doc1.set(std::string("The size of this string is 32!!")); doc1.set(std::string("The size of this string is 32!!"));
BasicJsonDocument<SpyingAllocator> doc2(8, log); BasicJsonDocument doc2(8, &spyingAllocator);
doc2 = doc1; doc2 = doc1;
@ -96,14 +110,14 @@ TEST_CASE("BasicJsonDocument") {
REQUIRE(doc2.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(doc2.capacity() == 4096);
} }
REQUIRE(log.str() == "A4096A8FA4096FF"); REQUIRE(spyingAllocator.log() == "A4096A8FA4096FF");
} }
SECTION("Copy assign smaller") { SECTION("Copy assign smaller") {
{ {
BasicJsonDocument<SpyingAllocator> doc1(1024, log); BasicJsonDocument doc1(1024, &spyingAllocator);
doc1.set(std::string("The size of this string is 32!!")); doc1.set(std::string("The size of this string is 32!!"));
BasicJsonDocument<SpyingAllocator> doc2(4096, log); BasicJsonDocument doc2(4096, &spyingAllocator);
doc2 = doc1; doc2 = doc1;
@ -111,14 +125,14 @@ TEST_CASE("BasicJsonDocument") {
REQUIRE(doc2.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() == 1024); REQUIRE(doc2.capacity() == 1024);
} }
REQUIRE(log.str() == "A1024A4096FA1024FF"); REQUIRE(spyingAllocator.log() == "A1024A4096FA1024FF");
} }
SECTION("Copy assign same size") { 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!!")); doc1.set(std::string("The size of this string is 32!!"));
BasicJsonDocument<SpyingAllocator> doc2(1024, log); BasicJsonDocument doc2(1024, &spyingAllocator);
doc2 = doc1; doc2 = doc1;
@ -126,14 +140,14 @@ TEST_CASE("BasicJsonDocument") {
REQUIRE(doc2.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() == 1024); REQUIRE(doc2.capacity() == 1024);
} }
REQUIRE(log.str() == "A1024A1024FF"); REQUIRE(spyingAllocator.log() == "A1024A1024FF");
} }
SECTION("Move assign") { SECTION("Move assign") {
{ {
BasicJsonDocument<SpyingAllocator> doc1(4096, log); BasicJsonDocument doc1(4096, &spyingAllocator);
doc1.set(std::string("The size of this string is 32!!")); doc1.set(std::string("The size of this string is 32!!"));
BasicJsonDocument<SpyingAllocator> doc2(8, log); BasicJsonDocument doc2(8, &spyingAllocator);
doc2 = std::move(doc1); doc2 = std::move(doc1);
@ -142,11 +156,11 @@ TEST_CASE("BasicJsonDocument") {
REQUIRE(doc1.capacity() == 0); REQUIRE(doc1.capacity() == 0);
REQUIRE(doc2.capacity() == 4096); REQUIRE(doc2.capacity() == 4096);
} }
REQUIRE(log.str() == "A4096A8FF"); REQUIRE(spyingAllocator.log() == "A4096A8FF");
} }
SECTION("garbageCollect()") { SECTION("garbageCollect()") {
BasicJsonDocument<ControllableAllocator> doc(4096); BasicJsonDocument doc(4096, &controllableAllocator);
SECTION("when allocation succeeds") { SECTION("when allocation succeeds") {
deserializeJson(doc, "{\"blanket\":1,\"dancing\":2}"); deserializeJson(doc, "{\"blanket\":1,\"dancing\":2}");
@ -167,7 +181,7 @@ TEST_CASE("BasicJsonDocument") {
REQUIRE(doc.capacity() == 4096); REQUIRE(doc.capacity() == 4096);
REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16);
doc.remove("blanket"); doc.remove("blanket");
doc.allocator().disable(); controllableAllocator.disable();
bool result = doc.garbageCollect(); bool result = doc.garbageCollect();

View File

@ -8,24 +8,25 @@
#include <stdlib.h> // malloc, free #include <stdlib.h> // malloc, free
#include <string> #include <string>
class ArmoredAllocator { class ArmoredAllocator : public Allocator {
public: public:
ArmoredAllocator() : _ptr(0), _size(0) {} ArmoredAllocator() : _ptr(0), _size(0) {}
virtual ~ArmoredAllocator() {}
void* allocate(size_t size) { void* allocate(size_t size) override {
_ptr = malloc(size); _ptr = malloc(size);
_size = size; _size = size;
return _ptr; return _ptr;
} }
void deallocate(void* ptr) { void deallocate(void* ptr) override {
REQUIRE(ptr == _ptr); REQUIRE(ptr == _ptr);
free(ptr); free(ptr);
_ptr = 0; _ptr = 0;
_size = 0; _size = 0;
} }
void* reallocate(void* ptr, size_t new_size) { void* reallocate(void* ptr, size_t new_size) override {
REQUIRE(ptr == _ptr); REQUIRE(ptr == _ptr);
// don't call realloc, instead alloc a new buffer and erase the old one // don't call realloc, instead alloc a new buffer and erase the old one
// this way we make sure we support relocation // this way we make sure we support relocation
@ -42,9 +43,7 @@ class ArmoredAllocator {
size_t _size; size_t _size;
}; };
typedef BasicJsonDocument<ArmoredAllocator> ShrinkToFitTestDocument; void testShrinkToFit(DynamicJsonDocument& doc, std::string expected_json,
void testShrinkToFit(ShrinkToFitTestDocument& doc, std::string expected_json,
size_t expected_size) { size_t expected_size) {
// test twice: shrinkToFit() should be idempotent // test twice: shrinkToFit() should be idempotent
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
@ -60,7 +59,8 @@ void testShrinkToFit(ShrinkToFitTestDocument& doc, std::string expected_json,
} }
TEST_CASE("BasicJsonDocument::shrinkToFit()") { TEST_CASE("BasicJsonDocument::shrinkToFit()") {
ShrinkToFitTestDocument doc(4096); ArmoredAllocator armoredAllocator;
DynamicJsonDocument doc(4096, &armoredAllocator);
SECTION("null") { SECTION("null") {
testShrinkToFit(doc, "null", 0); testShrinkToFit(doc, "null", 0);

View File

@ -5,58 +5,60 @@
#pragma once #pragma once
#include <ArduinoJson/Document/JsonDocument.hpp> #include <ArduinoJson/Document/JsonDocument.hpp>
#include <ArduinoJson/Memory/Allocator.hpp>
#include <stdlib.h> // malloc, free
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
// Helper to implement the "base-from-member" idiom // Helper to implement the "base-from-member" idiom
// (we need to store the allocator before constructing JsonDocument) // (we need to store the allocator before constructing JsonDocument)
template <typename TAllocator>
class AllocatorOwner { class AllocatorOwner {
public: public:
AllocatorOwner() {} AllocatorOwner(Allocator* allocator) : _allocator(allocator) {}
AllocatorOwner(TAllocator a) : _allocator(a) {}
void* allocate(size_t size) { void* allocate(size_t size) {
return _allocator.allocate(size); return _allocator->allocate(size);
} }
void deallocate(void* ptr) { void deallocate(void* ptr) {
if (ptr) if (ptr)
_allocator.deallocate(ptr); _allocator->deallocate(ptr);
} }
void* reallocate(void* ptr, size_t new_size) { 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; return _allocator;
} }
private: private:
TAllocator _allocator; Allocator* _allocator;
}; };
// A JsonDocument that uses the provided allocator to allocate its memory pool. // A JsonDocument that uses the provided allocator to allocate its memory pool.
// https://arduinojson.org/v6/api/basicjsondocument/ // https://arduinojson.org/v6/api/basicjsondocument/
template <typename TAllocator> class BasicJsonDocument : AllocatorOwner, public JsonDocument {
class BasicJsonDocument : AllocatorOwner<TAllocator>, public JsonDocument {
public: public:
explicit BasicJsonDocument(size_t capa, TAllocator alloc = TAllocator()) explicit BasicJsonDocument(
: AllocatorOwner<TAllocator>(alloc), JsonDocument(allocPool(capa)) {} size_t capa, Allocator* alloc = detail::DefaultAllocator::instance())
: AllocatorOwner(alloc), JsonDocument(allocPool(capa)) {}
// Copy-constructor // Copy-constructor
BasicJsonDocument(const BasicJsonDocument& src) BasicJsonDocument(const BasicJsonDocument& src)
: AllocatorOwner<TAllocator>(src), JsonDocument() { : AllocatorOwner(src), JsonDocument() {
copyAssignFrom(src); copyAssignFrom(src);
} }
// Move-constructor // Move-constructor
BasicJsonDocument(BasicJsonDocument&& src) : AllocatorOwner<TAllocator>(src) { BasicJsonDocument(BasicJsonDocument&& src) : AllocatorOwner(src) {
moveAssignFrom(src); moveAssignFrom(src);
} }
BasicJsonDocument(const JsonDocument& src) { BasicJsonDocument(const JsonDocument& src)
: AllocatorOwner(detail::DefaultAllocator::instance()) {
copyAssignFrom(src); copyAssignFrom(src);
} }
@ -70,13 +72,14 @@ class BasicJsonDocument : AllocatorOwner<TAllocator>, public JsonDocument {
detail::is_same<T, JsonArrayConst>::value || detail::is_same<T, JsonArrayConst>::value ||
detail::is_same<T, JsonObject>::value || detail::is_same<T, JsonObject>::value ||
detail::is_same<T, JsonObjectConst>::value>::type* = 0) detail::is_same<T, JsonObjectConst>::value>::type* = 0)
: JsonDocument(allocPool(src.memoryUsage())) { : BasicJsonDocument(src.memoryUsage()) {
set(src); set(src);
} }
// disambiguate // disambiguate
BasicJsonDocument(JsonVariant src) BasicJsonDocument(JsonVariant src)
: JsonDocument(allocPool(src.memoryUsage())) { : AllocatorOwner(detail::DefaultAllocator::instance()),
JsonDocument(allocPool(src.memoryUsage())) {
set(src); set(src);
} }
@ -132,7 +135,7 @@ class BasicJsonDocument : AllocatorOwner<TAllocator>, public JsonDocument {
return true; return true;
} }
using AllocatorOwner<TAllocator>::allocator; using AllocatorOwner::allocator;
private: private:
detail::MemoryPool allocPool(size_t requiredSize) { detail::MemoryPool allocPool(size_t requiredSize) {

View File

@ -6,27 +6,10 @@
#include <ArduinoJson/Document/BasicJsonDocument.hpp> #include <ArduinoJson/Document/BasicJsonDocument.hpp>
#include <stdlib.h> // malloc, free
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE 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. // A JsonDocument with a memory pool in the heap.
// https://arduinojson.org/v6/api/dynamicjsondocument/ // https://arduinojson.org/v6/api/dynamicjsondocument/
typedef BasicJsonDocument<DefaultAllocator> DynamicJsonDocument; typedef BasicJsonDocument DynamicJsonDocument;
ARDUINOJSON_END_PUBLIC_NAMESPACE ARDUINOJSON_END_PUBLIC_NAMESPACE

View File

@ -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