Require types used with construct are trivially destructible
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <new>
|
#include <new>
|
||||||
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,8 +37,9 @@
|
|||||||
* ArenaAllocator arena(1024);
|
* ArenaAllocator arena(1024);
|
||||||
* void* ptr = arena.allocate(100);
|
* void* ptr = arena.allocate(100);
|
||||||
*
|
*
|
||||||
* // Construct objects in-place
|
* // Construct trivially destructible objects in-place
|
||||||
* MyClass* obj = arena.construct<MyClass>(arg1, arg2);
|
* int* num = arena.construct<int>(42);
|
||||||
|
* MyPOD* obj = arena.construct<MyPOD>(arg1, arg2); // If MyPOD is trivial
|
||||||
*
|
*
|
||||||
* // Track memory usage
|
* // Track memory usage
|
||||||
* size_t total = arena.total_allocated();
|
* size_t total = arena.total_allocated();
|
||||||
@@ -231,23 +233,36 @@ public:
|
|||||||
* This is a convenience method that combines allocation with in-place
|
* This is a convenience method that combines allocation with in-place
|
||||||
* construction. It properly handles alignment requirements for type T.
|
* construction. It properly handles alignment requirements for type T.
|
||||||
*
|
*
|
||||||
* @tparam T The type of object to construct
|
* @tparam T The type of object to construct (must be trivially destructible)
|
||||||
* @tparam Args Types of constructor arguments
|
* @tparam Args Types of constructor arguments
|
||||||
* @param args Arguments to forward to T's constructor
|
* @param args Arguments to forward to T's constructor
|
||||||
* @return Pointer to the constructed object
|
* @return Pointer to the constructed object
|
||||||
* @throws std::bad_alloc if memory allocation fails
|
* @throws std::bad_alloc if memory allocation fails
|
||||||
*
|
*
|
||||||
|
* ## Type Requirements:
|
||||||
|
* T must be trivially destructible (std::is_trivially_destructible_v<T>).
|
||||||
|
* This prevents subtle bugs since destructors are never called for objects
|
||||||
|
* constructed in the arena.
|
||||||
|
*
|
||||||
* ## Example:
|
* ## Example:
|
||||||
* ```cpp
|
* ```cpp
|
||||||
* std::string* str = arena.construct<std::string>("Hello, World!");
|
* int* num = arena.construct<int>(42); // ✓ Trivially
|
||||||
* MyClass* obj = arena.construct<MyClass>(arg1, arg2, arg3);
|
* destructible MyPOD* pod = arena.construct<MyPOD>(arg1, arg2); // ✓ If
|
||||||
|
* MyPOD is trivial std::string* str = arena.construct<std::string>("hi"); //
|
||||||
|
* ✗ Compile error!
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* ## Note:
|
* ## Note:
|
||||||
* Objects constructed this way cannot be individually destroyed.
|
* Objects constructed this way cannot be individually destroyed.
|
||||||
* Their destructors will NOT be called automatically.
|
* Their destructors will NOT be called automatically - hence the requirement
|
||||||
|
* for trivially destructible types.
|
||||||
*/
|
*/
|
||||||
template <typename T, typename... Args> T *construct(Args &&...args) {
|
template <typename T, typename... Args> T *construct(Args &&...args) {
|
||||||
|
static_assert(
|
||||||
|
std::is_trivially_destructible_v<T>,
|
||||||
|
"ArenaAllocator::construct requires trivially destructible types. "
|
||||||
|
"Objects constructed in the arena will not have their destructors "
|
||||||
|
"called.");
|
||||||
void *ptr = allocate(sizeof(T), alignof(T));
|
void *ptr = allocate(sizeof(T), alignof(T));
|
||||||
return new (ptr) T(std::forward<Args>(args)...);
|
return new (ptr) T(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||||
#include "arena_allocator.hpp"
|
#include "arena_allocator.hpp"
|
||||||
|
#include <cstring>
|
||||||
#include <doctest/doctest.h>
|
#include <doctest/doctest.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -106,10 +107,17 @@ TEST_CASE("ArenaAllocator construct template") {
|
|||||||
CHECK(*ptr == 42);
|
CHECK(*ptr == 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
SUBCASE("construct string") {
|
SUBCASE("construct array") {
|
||||||
std::string *ptr = arena.construct<std::string>("hello world");
|
struct FixedString {
|
||||||
|
char data[16];
|
||||||
|
FixedString(const char *str) {
|
||||||
|
std::strncpy(data, str, sizeof(data) - 1);
|
||||||
|
data[sizeof(data) - 1] = '\0';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FixedString *ptr = arena.construct<FixedString>("hello world");
|
||||||
CHECK(ptr != nullptr);
|
CHECK(ptr != nullptr);
|
||||||
CHECK(*ptr == "hello world");
|
CHECK(std::strcmp(ptr->data, "hello world") == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
SUBCASE("construct multiple objects") {
|
SUBCASE("construct multiple objects") {
|
||||||
@@ -122,10 +130,15 @@ TEST_CASE("ArenaAllocator construct template") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SUBCASE("construct with multiple arguments") {
|
SUBCASE("construct with multiple arguments") {
|
||||||
auto *ptr = arena.construct<std::pair<int, std::string>>(42, "test");
|
struct IntPair {
|
||||||
|
int first;
|
||||||
|
int second;
|
||||||
|
IntPair(int a, int b) : first(a), second(b) {}
|
||||||
|
};
|
||||||
|
auto *ptr = arena.construct<IntPair>(42, 24);
|
||||||
CHECK(ptr != nullptr);
|
CHECK(ptr != nullptr);
|
||||||
CHECK(ptr->first == 42);
|
CHECK(ptr->first == 42);
|
||||||
CHECK(ptr->second == "test");
|
CHECK(ptr->second == 24);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,27 +279,29 @@ TEST_CASE("ArenaAllocator edge cases") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TestObject {
|
struct TestPOD {
|
||||||
int value;
|
int value;
|
||||||
std::string name;
|
char name[16];
|
||||||
|
|
||||||
TestObject(int v, const std::string &n) : value(v), name(n) {}
|
TestPOD(int v, const char *n) : value(v) {
|
||||||
~TestObject() = default;
|
std::strncpy(name, n, sizeof(name) - 1);
|
||||||
|
name[sizeof(name) - 1] = '\0';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_CASE("ArenaAllocator with custom objects") {
|
TEST_CASE("ArenaAllocator with custom objects") {
|
||||||
ArenaAllocator arena;
|
ArenaAllocator arena;
|
||||||
|
|
||||||
TestObject *obj1 = arena.construct<TestObject>(42, "first");
|
TestPOD *obj1 = arena.construct<TestPOD>(42, "first");
|
||||||
TestObject *obj2 = arena.construct<TestObject>(84, "second");
|
TestPOD *obj2 = arena.construct<TestPOD>(84, "second");
|
||||||
|
|
||||||
CHECK(obj1 != nullptr);
|
CHECK(obj1 != nullptr);
|
||||||
CHECK(obj2 != nullptr);
|
CHECK(obj2 != nullptr);
|
||||||
CHECK(obj1 != obj2);
|
CHECK(obj1 != obj2);
|
||||||
CHECK(obj1->value == 42);
|
CHECK(obj1->value == 42);
|
||||||
CHECK(obj1->name == "first");
|
CHECK(std::strcmp(obj1->name, "first") == 0);
|
||||||
CHECK(obj2->value == 84);
|
CHECK(obj2->value == 84);
|
||||||
CHECK(obj2->name == "second");
|
CHECK(std::strcmp(obj2->name, "second") == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("ArenaAllocator geometric growth policy") {
|
TEST_CASE("ArenaAllocator geometric growth policy") {
|
||||||
|
|||||||
Reference in New Issue
Block a user