-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented type erasure for allocators
- Loading branch information
1 parent
d3d3bec
commit b989598
Showing
4 changed files
with
246 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
// Copyright 2024 Man Group Operations Limited | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#pragma once | ||
|
||
#include <cstdint> | ||
#include <memory> | ||
#include <type_traits> | ||
|
||
namespace sparrow | ||
{ | ||
/* | ||
* allocator concept, based on the requirements specified by the standard: | ||
* https://en.cppreference.com/w/cpp/named_req/Allocator. | ||
*/ | ||
template <class T> | ||
concept allocator = std::copy_constructible<std::remove_cvref_t<T>> | ||
and std::move_constructible<std::remove_cvref_t<T>> | ||
and std::equality_comparable<std::remove_cvref_t<T>> | ||
and requires(std::remove_cvref_t<T>& alloc, | ||
typename std::remove_cvref_t<T>::value_type* p, | ||
std::size_t n) | ||
{ | ||
typename std::remove_cvref_t<T>::value_type; | ||
{ alloc.allocate(n) } -> std::same_as<typename std::remove_cvref_t<T>::value_type*>; | ||
{ alloc.deallocate(p, n) } -> std::same_as<void>; | ||
}; | ||
|
||
/* | ||
* Type erasure class for allocators. This allows to use any kind of allocator | ||
* (standard, polymorphic) whithout having to expose it as a template parameter. | ||
* | ||
* @tparam T value_type of the allocator | ||
* @tparam DA default allocator, instantiated when calling the default constructor | ||
*/ | ||
template <class T, class DA = std::allocator<T>> | ||
class any_allocator | ||
{ | ||
public: | ||
|
||
using value_type = T; | ||
|
||
any_allocator() | ||
: p_allocator(std::make_unique<impl<DA>>(DA())) | ||
{ | ||
} | ||
|
||
any_allocator(const any_allocator& rhs) | ||
: p_allocator(rhs.p_allocator->clone()) | ||
{ | ||
} | ||
|
||
any_allocator& operator=(const any_allocator& rhs) | ||
{ | ||
p_allocator = rhs.p_allocator->clone(); | ||
return *this; | ||
} | ||
|
||
any_allocator(any_allocator&&) = default; | ||
any_allocator& operator=(any_allocator&&) = default; | ||
|
||
template <class A> | ||
any_allocator(A&& alloc) | ||
requires (not std::same_as<std::remove_cvref_t<A>, any_allocator> | ||
and sparrow::allocator<A>) | ||
: p_allocator(std::make_unique<impl<std::decay_t<A>>>(std::forward<A>(alloc))) | ||
{ | ||
} | ||
|
||
[[nodiscard]] T* allocate(std::size_t n) | ||
{ | ||
return p_allocator->allocate(n); | ||
} | ||
|
||
void deallocate(T* p, std::size_t n) | ||
{ | ||
p_allocator->deallocate(p, n); | ||
} | ||
|
||
bool equal(const any_allocator& rhs) const | ||
{ | ||
return p_allocator->equal(*rhs.p_allocator); | ||
} | ||
|
||
private: | ||
|
||
struct interface | ||
{ | ||
[[nodiscard]] virtual T* allocate(std::size_t) = 0; | ||
virtual void deallocate(T*, std::size_t) = 0; | ||
virtual std::unique_ptr<interface> clone() const = 0; | ||
virtual bool equal(const interface&) const = 0; | ||
virtual ~interface() = default; | ||
}; | ||
|
||
template <allocator A> | ||
struct impl : interface | ||
{ | ||
A m_alloc; | ||
|
||
explicit impl(A alloc) : m_alloc(std::move(alloc)) {} | ||
|
||
[[nodiscard]] T* allocate(std::size_t n) override | ||
{ | ||
return m_alloc.allocate(n); | ||
} | ||
|
||
void deallocate(T* p, std::size_t n) override | ||
{ | ||
m_alloc.deallocate(p, n); | ||
} | ||
|
||
std::unique_ptr<interface> clone() const override | ||
{ | ||
return std::make_unique<impl<A>>(m_alloc); | ||
} | ||
|
||
bool equal(const interface& rhs) const override | ||
{ | ||
if (auto* p = dynamic_cast<const impl<A>*>(&rhs)) | ||
{ | ||
return p->m_alloc == m_alloc; | ||
} | ||
return false; | ||
} | ||
}; | ||
|
||
std::unique_ptr<interface> p_allocator; | ||
}; | ||
|
||
template <class T, class DA> | ||
bool operator==(const any_allocator<T, DA>& lhs, const any_allocator<T, DA>& rhs) | ||
{ | ||
return lhs.equal(rhs); | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// Copyright 2024 Man Group Operations Limited | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#include "doctest/doctest.h" | ||
|
||
#include <memory> | ||
#include <memory_resource> | ||
#include <numeric> | ||
#include <vector> | ||
|
||
#include "sparrow/allocator.hpp" | ||
|
||
TEST_SUITE("any_allocator") | ||
{ | ||
TEST_CASE_TEMPLATE_DEFINE("value semantic", A, value_semantic_id) | ||
{ | ||
SUBCASE("constructor") | ||
{ | ||
{ | ||
sparrow::any_allocator<int> a; | ||
} | ||
{ | ||
A alloc; | ||
sparrow::any_allocator<typename A::value_type> a(alloc); | ||
} | ||
} | ||
|
||
SUBCASE("copy semantic") | ||
{ | ||
using value_type = typename A::value_type; | ||
|
||
A alloc; | ||
sparrow::any_allocator<value_type> a(alloc); | ||
sparrow::any_allocator<value_type> b(a); | ||
CHECK(a == b); | ||
|
||
sparrow::any_allocator<value_type> c(alloc); | ||
b = c; | ||
CHECK(b == c); | ||
} | ||
|
||
SUBCASE("move semantic") | ||
{ | ||
using value_type = typename A::value_type; | ||
|
||
A alloc; | ||
sparrow::any_allocator<value_type> a(alloc); | ||
sparrow::any_allocator<value_type> aref(a); | ||
sparrow::any_allocator<value_type> b(std::move(a)); | ||
CHECK_EQ(b, aref); | ||
|
||
sparrow::any_allocator<value_type> c(aref); | ||
b = std::move(c); | ||
CHECK_EQ(b, aref); | ||
} | ||
} | ||
|
||
TEST_CASE_TEMPLATE_DEFINE("allocate / deallocate", A, allocate_id) | ||
{ | ||
using value_type = typename A::value_type; | ||
|
||
constexpr std::size_t n = 100; | ||
std::vector<value_type> ref(n); | ||
std::iota(ref.begin(), ref.end(), value_type()); | ||
|
||
A alloc; | ||
sparrow::any_allocator<value_type> a(alloc); | ||
value_type* buf = a.allocate(n); | ||
std::uninitialized_copy(ref.cbegin(), ref.cend(), buf); | ||
CHECK_EQ(*buf, ref[0]); | ||
CHECK_EQ(*(buf + n - 1), ref.back()); | ||
a.deallocate(buf, n); | ||
} | ||
|
||
#if __APPLE__ | ||
// /usr/lib/libc++.1.dylib is missing the symbol __ZNSt3__13pmr20get_default_resourceEv, leading | ||
// to an exception at runtime. | ||
TEST_CASE_TEMPLATE_INVOKE(value_semantic_id, std::allocator<int>/*, std::pmr::polymorphic_allocator<int>*/); | ||
TEST_CASE_TEMPLATE_INVOKE(allocate_id, std::allocator<int>/*, std::pmr::polymorphic_allocator<int>*/); | ||
#else | ||
TEST_CASE_TEMPLATE_INVOKE(value_semantic_id, std::allocator<int>, std::pmr::polymorphic_allocator<int>); | ||
TEST_CASE_TEMPLATE_INVOKE(allocate_id, std::allocator<int>, std::pmr::polymorphic_allocator<int>); | ||
#endif | ||
} | ||
|