Линейный распределитель:
#pragma once
#include <cstddef>
#include <cassert>
#include <new>
#include <algorithm>
#include "aligned_allocations.hpp"
#include <utility>
#include <iostream>
template <typename T, std::size_t alignment = alignof(std::max_align_t)>
class LinearAllocator
{
T* m_pstart;
std::size_t m_offset;
std::size_t m_maxSize;
public:
using value_type = T;
using pointer = T*;
using propagate_on_container_copy_assignment = std::true_type;
using propagate_on_container_move_assignment = std::true_type;
using propagate_on_container_swap = std::true_type;
LinearAllocator() = default;
LinearAllocator(const std::size_t totalSize) noexcept
: m_pstart{ nullptr },
m_offset{ 0 },
m_maxSize{ totalSize }
{
static_assert(isPowerOfTwo(alignment), "Alignment value must be a power of 2.");
init();
}
~LinearAllocator() noexcept
{
reset();
alignedFree(m_pstart);
m_pstart = nullptr;
}
void init()
{
if (m_pstart != nullptr)
{
reset();
alignedFree(m_pstart);
}
m_pstart = static_cast<T*>(alignedMalloc(m_maxSize, getAlignment()));
assert(isAligned(m_pstart, getAlignment()));
}
void reset() noexcept
{
m_offset = 0;
}
template<typename Other, std::size_t OtherAlignment = alignof(std::max_align_t)>
struct rebind {
using other = LinearAllocator<Other, OtherAlignment>;
};
LinearAllocator(LinearAllocator& rhs) noexcept
: m_pstart{ static_cast<T*>(alignedMalloc(rhs.getMaxSize(), getAlignment())) },
m_offset{ rhs.getOffset() },
m_maxSize{ rhs.getMaxSize() }
{
assert(isAligned(m_pstart, getAlignment()));
}
template<typename Other, std::size_t OtherAlignment>
LinearAllocator(const LinearAllocator<Other, OtherAlignment>& rhs) noexcept
: m_pstart{ static_cast<T*>(alignedMalloc(rhs.getMaxSize(), OtherAlignment)) },
m_offset{ rhs.getOffset() },
m_maxSize{ rhs.getMaxSize() }
{
assert(isAligned(m_pstart, OtherAlignment));
}
template<typename Other, std::size_t OtherAlignment>
LinearAllocator& operator=(LinearAllocator<Other, OtherAlignment>& rhs) noexcept
{
if (this != &rhs)
{
m_pstart = static_cast<T*>(alignedMalloc(rhs.getMaxSize(), OtherAlignment));
m_offset = rhs.getOffset();
m_maxSize = rhs.getMaxSize();
assert(isAligned(m_pstart, OtherAlignment));
}
return *this;
}
LinearAllocator(LinearAllocator&& rhs) noexcept
: m_pstart{ rhs.getStartAddress() },
m_offset{ rhs.getOffset() },
m_maxSize{ rhs.getMaxSize() }
{
assert(isAligned(m_pstart, getAlignment()));
}
template<typename Other, std::size_t OtherAlignment>
LinearAllocator(LinearAllocator<Other, OtherAlignment>&& rhs) noexcept
: m_pstart{ rhs.getStartAddress() },
m_offset{ rhs.getOffset() },
m_maxSize{ rhs.getMaxSize() }
{
assert(isAligned(m_pstart, OtherAlignment));
}
template<typename Other, std::size_t OtherAlignment>
LinearAllocator& operator=(LinearAllocator<Other, OtherAlignment>&& rhs) noexcept
{
if (this != &rhs)
{
m_pstart = rhs.getStartAddress();
m_offset = rhs.getOffset();
m_maxSize = rhs.getMaxSize();
assert(isAligned(m_pstart, OtherAlignment));
}
return *this;
}
[[nodiscard]]
T* allocate(const std::size_t bytes)
{
assert(m_pstart != nullptr);
std::size_t currentAddress = getCurrentAddress();
std::size_t userPtr = reinterpret_cast<std::size_t>(alignForward(
reinterpret_cast<T*>(currentAddress + bytes), getAlignment()));
assert(isAligned(userPtr, getAlignment()));
// check if there is enough memory available
if (userPtr > getEndAddress())
{
throw std::bad_alloc{};
}
m_offset += userPtr - currentAddress;
return reinterpret_cast<T*>(currentAddress);
}
void deallocate([[maybe_unused]] T* p, [[maybe_unused]] const std::size_t count) noexcept
{
assert(false);
reset();
}
//[[deprecated("construct() is deprecated in C++17 and will be removed in C++20")]]
template<typename J, typename... Args>
void construct(J* p, Args&&... args) const
{
new(p) J(std::forward<Args>(args)...);
}
template<typename J>
void destroy(J* p) const noexcept
{
p->~J();
}
template<typename... Args>
void construct(T* const p, Args&&... args) const
{
new(p) T(std::forward<Args>(args)...);
}
void destroy(T* const p) const noexcept
{
p->~T();
}
/// \brief HELPERS
inline std::size_t getAvailableMemory() const noexcept {
return getEndAddress() - getCurrentAddress();
}
inline std::size_t getCurrentAddress() const noexcept {
return reinterpret_cast<std::size_t>(m_pstart) + m_offset;
}
inline std::size_t getEndAddress() const noexcept {
return reinterpret_cast<std::size_t>(m_pstart) + m_maxSize;
}
inline constexpr std::size_t getAlignment() const noexcept {
return (alignment > sizeof(void*)) ? alignment : sizeof(void*);
}
/// \brief GETTERS
inline T* getStartAddress() const noexcept {
return m_pstart;
}
inline std::size_t getOffset() const noexcept {
return m_offset;
}
inline std::size_t getMaxSize() const noexcept {
return m_maxSize;
}
};
template<typename T, std::size_t alignment1, typename Other, std::size_t alignment2>
inline bool operator==(const LinearAllocator<T, alignment1>& lhs, const LinearAllocator<Other, alignment2>& rhs) noexcept
{
return lhs.getStartAddress() == rhs.getStartAddress();
}
template<typename T, std::size_t alignment1, typename Other, std::size_t alignment2>
inline bool operator!=(const LinearAllocator<T, alignment1>& lhs, const LinearAllocator<Other, alignment2>& rhs) noexcept
{
return lhs.getStartAddress() != rhs.getStartAddress();
}
тесты:
#include "linear_allocator.hpp"
#include <deque>
#include <iostream>
#include <string>
#include <typeinfo>
#include <utility>
int main()
{
std::cout << std::boolalpha << '\n';
std::cout << "Individual allocations" << '\n';
[[maybe_unused]]
LinearAllocator<int, 256> la{ 1024 };
std::cout << la.getStartAddress() << '\n';
std::cout << la.getCurrentAddress() << '\n';
std::cout << la.getAvailableMemory() << '\n';
std::cout << la.getMaxSize() << '\n';
std::cout << la.getAlignment() << '\n';
std::cout << "int fundamental type example" << '\n';
void* vi = la.allocate(sizeof(int));
int* pi = new(vi) int{ 4 };
std::cout << *pi << '\n';
std::cout << "isAligned(pi, 256)=" << isAligned(pi, 256) << '\n';
int* pint = la.allocate(sizeof(int));
*pint = 1453;
std::cout << *pint << '\n';
std::cout << "isAligned(pint, 256)=" << isAligned(pint, 256) << '\n';
std::cout << "\nstd::string example" << '\n';
std::cout << "\nAlign each element of an array:" << '\n';
constexpr std::size_t intAlignment = 16;
struct AlignedInt {
alignas(intAlignment) int aint;
};
LinearAllocator<AlignedInt, intAlignment> LA{ sizeof(AlignedInt) * intAlignment * 4 * 64 };
AlignedInt* palignedInts = LA.allocate(sizeof(AlignedInt) * 64);
for (int i = 0; i < 64; i++)
(palignedInts + sizeof(AlignedInt) * i)->aint = i;
for (int i = 0; i < 64; i++)
std::cout << (palignedInts + sizeof(AlignedInt) * i)->aint << ' ' << isAligned((palignedInts + sizeof(AlignedInt) * i), intAlignment) << '\n';
// it's aligned!
// everything up to this point works
// FAILS:
using lstring = std::basic_string<char, std::char_traits<char>, std::allocator<char>>;
LinearAllocator<std::string, 512> sla{ 789430 };
std::string* ps = sla.allocate(8192);
*ps = "My name is Maximus Decimus Meridius.\n"; // fails here
std::cout << *ps << '\n';
// FAILS:
LinearAllocator<char, 16> LC{ sizeof(char) * 16 * 1024 };
std::deque<char, decltype(LC)> dq{ 100, LC }; // fails here
assert(isAligned(&dq, 16));
dq.push_front('r');
dq.push_front('e');
dq.push_front('m');
dq.push_front('e');
dq.push_front('m');
dq.push_front('b');
dq.push_front('e');
dq.push_front('r');
dq.push_front('m');
dq.push_front('e');
for (const auto &i : dq)
{
std::cout << i << '\n';
}
std::cout << typeid(dq.get_allocator()).name() << '\n';
}
Как вы можете видеть, он выходит из строя, когда я поставляю ему std::queue
, он также отказывает, если я его поставляюstd::string
.Это работает только для фундаментальных типов и типов, которые я создаю, которые не нуждаются в распределителе.Вы можете видеть это в тестах.Я использую последнюю версию MSVS 2017, используя C ++ 17.Эта ошибка во время выполнения явно заставляет меня поверить, что есть какое-то требование к распределителю, которое я не выполняю, и поэтому мой распределитель остается несовместимым со стандартными контейнерами библиотеки и другими средствами.
Кстати, я знаю, что многие изфункции, которые я предоставил в распределителе, не нужны, так как они поставляются allocator_traits
.Тем не менее, я добавил их на тот случай, если проблема была глубже внутри библиотеки, но я всегда получаю одну и ту же ошибку.Я отладил, и упоминаются строки, где это терпит неудачу - 2 строки, 1 для std::string
1 для std::queue
.
Я не могу найти, в чем проблема.Компилятор также не имеет претензий.Может кто-нибудь помочь?
PS.
Еще одна вещь, которая сейчас может быть неактуальной.Структура rebind
предположительно не нужна, поскольку она предоставляется allocator_traits
.Однако мой распределитель имеет 2 параметра шаблона, и я не думаю, что за это отвечает allocator_traits
, он учитывает только 1 параметр шаблона T
.Если я либо удалю rebind
, либо сделаю его второй параметр шаблона параметром не по умолчанию (по умолчанию alignof(std::max_align_it)
сейчас), появится много ошибок, например: Severity Code Description Project File Line Suppression State
Error C2027 use of undefined type 'std::_Replace_first_parameter<_Other,_Ty>' Allocators c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\xmemory0 553
и Severity Code Description Project File Line Suppression State
Error C2061 syntax error: identifier 'type' Allocators c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\xmemory0 553
, Severity Code Description Project File Line Suppression State
Error C2903 '_Is_simple_alloc_v': symbol is neither a class template nor a function template Allocators c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\deque 652
и еще 20..