From 7494a7fd962287362a496a38c65bf24a8d190479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arsen=20Arsenovi=C4=87?= Date: Mon, 10 Apr 2023 18:08:50 +0200 Subject: [PATCH] unique: Add conversion constructors and tests --- include/frg/unique.hpp | 40 ++++++++++++++++++-- tests/tests.cpp | 86 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 3 deletions(-) diff --git a/include/frg/unique.hpp b/include/frg/unique.hpp index 2dd36e6..f93b6cb 100644 --- a/include/frg/unique.hpp +++ b/include/frg/unique.hpp @@ -1,7 +1,9 @@ #ifndef FRG_UNIQUE_HPP #define FRG_UNIQUE_HPP +#include #include +#include namespace frg { @@ -19,9 +21,38 @@ struct unique_ptr { unique_ptr(Allocator allocator, T *ptr) :_ptr{ptr}, _allocator(std::move(allocator)) {} + /** Converts a unique_ptr of a different type to the type of this + unique_ptr. + + The allocator is taken from the old unique_ptr. It must be + convertible to our allocator type. + + The pointer type also, naturally, has to be convertible to the + current pointer type. + */ + template + requires ( + std::is_convertible_v + && std::is_constructible_v + ) + unique_ptr(unique_ptr&& o) + : _ptr { nullptr }, _allocator { std::move(o._allocator) } { + reset(o.release()); + } + + /** \sa unique_ptr(unique_ptr&&) */ + template + requires std::constructible_from&&> + unique_ptr& operator=(unique_ptr&& o) { + auto optr = o.release(); + reset(optr); + _allocator = std::forward(o._allocator); + return *this; + } + + ~unique_ptr() { - if (_ptr) - _allocator.free(_ptr); + reset(); } unique_ptr(const unique_ptr &) = delete; @@ -60,7 +91,7 @@ struct unique_ptr { return _ptr; } - void reset(T *ptr) { + void reset(T *ptr = nullptr) { T *old = _ptr; _ptr = ptr; @@ -71,6 +102,9 @@ struct unique_ptr { private: T *_ptr; Allocator _allocator; + + template + friend struct unique_ptr; }; template diff --git a/tests/tests.cpp b/tests/tests.cpp index c2b7cf4..c8e403d 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -3,6 +3,7 @@ #include #include +#include using string = frg::string; @@ -319,3 +320,88 @@ TEST(bitset, count) { EXPECT_FALSE(a.all()); EXPECT_FALSE(a.none()); } + +#include + +#include + +struct TestAlloc; + +struct TestPool { + std::unordered_map _allocated; + std::size_t checknum = 0; + + TestPool() + : _allocated {} {} + + void do_check() { + ASSERT_TRUE (_allocated.empty()) + << "Memory leak?! In " << (checknum++); + } + + TestPool& operator=(const TestPool&) = delete; + TestPool(const TestPool&) = delete; + TestPool& operator=(TestPool&& o) { + std::swap(_allocated, o._allocated); + return *this; + } + TestPool(TestPool&& o) { + operator=(std::move (o)); + } + + void* allocate(std::size_t sz) { + auto x = operator new(sz); + _allocated.emplace(x, sz); + std::cerr << "Allocated " << sz << " bytes at " << x + << std::endl; + return x; + } + + void free(void* x) { + std::cerr << "Deallocating " << x << std::endl; + auto xi = _allocated.find(x); + ASSERT_FALSE(xi == _allocated.cend()) + << "Double or invalid free of " << x; + _allocated.erase(xi); + operator delete(x); + } + + TestAlloc get(); +}; + +struct TestAlloc { + TestPool* tp; + + void* allocate(std::size_t sz) { + return tp->allocate(sz); + } + + void free(void* x) { + tp->free(x); + } +}; + +TestAlloc TestPool::get() { + return { this }; +} + +struct Base {}; +struct Derived : Base {}; + +TEST(unique_ptr, construction_destruction) { + using namespace frg; + TestPool tp; + + { + auto a = frg::make_unique(tp.get()); + auto b = std::move(a); + a = frg::make_unique(tp.get()); + b = frg::make_unique(tp.get()); + + tp.get().free(b.release()); + ASSERT_FALSE(b && b.get() == nullptr); + a.reset(); + ASSERT_FALSE(a && a.get() == nullptr); + }; + tp.do_check(); +}