Skip to content

Commit

Permalink
Implemented type erasure for allocators
Browse files Browse the repository at this point in the history
  • Loading branch information
JohanMabille committed Apr 1, 2024
1 parent d3d3bec commit b989598
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 0 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ OPTION(BUILD_TESTS "sparrow test suite" OFF)
# =====

set(SPARROW_HEADERS
${SPARROW_INCLUDE_DIR}/sparrow/allocator.hpp
${SPARROW_INCLUDE_DIR}/sparrow/array_data.hpp
${SPARROW_INCLUDE_DIR}/sparrow/buffer.hpp
${SPARROW_INCLUDE_DIR}/sparrow/fixed_size_layout.hpp
Expand Down
148 changes: 148 additions & 0 deletions include/sparrow/allocator.hpp
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);
}
}

1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ endif()

set(SPARROW_TESTS_SOURCES
main.cpp
test_allocator.cpp
test_array_data.cpp
test_buffer.cpp
test_dynamic_bitset.cpp
Expand Down
96 changes: 96 additions & 0 deletions test/test_allocator.cpp
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
}

0 comments on commit b989598

Please sign in to comment.