C++11 兼容的线性分配器实现
C++11 compatible Linear Allocator Implementation
我已经实现了一个 C++11 兼容的线性或竞技场分配器。代码如下。
linear_allocator.hpp:
#pragma once
#include <cstddef>
#include <cassert>
#include <new>
#include "aligned_mallocations.hpp"
template <typename T>
class LinearAllocator
{
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const 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(std::size_t count = 64)
: m_memUsed(0),
m_memStartAddress(nullptr)
{
allocate(count);
}
~LinearAllocator()
{
clear();
}
template <class U>
LinearAllocator(const LinearAllocator<U>&) noexcept
{}
/// \brief allocates memory equal to # count objects of type T
pointer allocate(std::size_t count)
{
if (count > std::size_t(-1) / sizeof(T))
{
throw std::bad_alloc{};
}
if (m_memStartAddress != nullptr)
{
alignedFree(m_memStartAddress);
}
m_memUsed = count * sizeof(T);
m_memStartAddress = static_cast<pointer>(alignedMalloc(m_memUsed, alignof(T)));
return m_memStartAddress;
}
/// \brief deallocates previously allocated memory
/// \brief Linear/arena allocators do not support free() operations. Use clear() instead.
void deallocate([[maybe_unused]] pointer p, [[maybe_unused]] std::size_t count) noexcept
{
//assert(false);
clear();
}
/// \brief simply resets memory
void clear()
{
if (m_memStartAddress != nullptr)
{
alignedFree(m_memStartAddress);
m_memStartAddress = nullptr;
}
this->m_memUsed = 0;
}
/// \brief GETTERS
pointer getStartAddress() const
{
return this->m_memStartAddress;
}
std::size_t getUsedMemory() const
{
return this->m_memUsed;
}
private:
std::size_t m_memUsed;
pointer m_memStartAddress;
};
template <class T, class U>
bool operator==(const LinearAllocator<T> &, const LinearAllocator<U> &)
{
return true;
}
template <class T, class U>
bool operator!=(const LinearAllocator<T> &, const LinearAllocator<U> &)
{
return false;
}
不用担心 alignedMalloc
和 alignedFree
。他们是正确的。
这是我的测试程序(linear_allocator.cpp):
#include "linear_allocator.hpp"
#include <vector>
#include <deque>
#include <iostream>
#include <string>
#include <typeinfo>
int main()
{
[[maybe_unused]]
LinearAllocator<int> a{1024};
std::cout << a.getStartAddress() << '\n';
std::cout << a.getUsedMemory() << '\n';
std::vector<std::string, LinearAllocator<std::string>> v;
v.reserve(100);
std::cout << "Vector capacity = " << v.capacity() << '\n';
//std::cout << v.get_allocator().getStartAddress() << '\n';
//std::cout << v.get_allocator().getUsedMemory() << '\n';
v.push_back("Hello");
v.push_back("w/e");
v.push_back("whatever");
v.push_back("there is ist sofi j");
v.push_back("wisdom");
v.push_back("fear");
v.push_back("there's more than meets the eye");
for (const auto &s : v)
{
std::cout << s << '\n';
}
std::cout << typeid(v.get_allocator()).name() << '\n';
std::deque<int, LinearAllocator<int>> dq;
dq.push_back(23);
dq.push_back(90);
dq.push_back(38794);
dq.push_back(7);
dq.push_back(0);
dq.push_back(2);
dq.push_back(13);
dq.push_back(24323);
dq.push_back(0);
dq.push_back(1234);
for (const auto &i : dq)
{
std::cout << i << '\n';
}
std::cout << typeid(dq.get_allocator()).name() << '\n';
}
使用 g++ -std=c++17 -O2 -march=native -Wall linear_allocator.cpp -o linear_allocator.gpp.exe
和 运行 linear_allocator.gpp.exe 编译得到输出:
0x4328b8
4096
Vector capacity = 100
Hello
w/e
whatever
there is ist sofi j
wisdom
fear
there's more than meets the eye
15LinearAllocatorINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE
如您所见,deque 的输出根本不存在。如果我取消注释这两行:
//std::cout << v.get_allocator().getStartAddress() << '\n';
//std::cout << v.get_allocator().getUsedMemory() << '\n';
矢量的输出也不会显示。
使用 MSVS cl 编译给出以下输出:
000000B47A1CAF88
4096
更糟糕的是
一定是我遗漏了什么,因为似乎有 UB,但我无法确定它在哪里。我的分配器设计基于 C++11+ 指南。我想知道我做错了什么。
虽然分配器负责提供和释放用于存储容器数据的内存,但它仍然只在容器请求时才这样做。也就是说,所提供存储的实际管理(特别是其生命周期)仍在容器方面。想象一下当向量执行其元素的重定位时会发生什么:
请求新的内存块,比当前(旧)内存块大一个给定因子。
存储在"old"块中的元素是copied/moved到新块。
才可以释放"old"内存块。
在您的实现中,一次只能有一个内存块处于活动状态——旧内存块在分配新内存块之前被释放(具体来说,当容器仅请求新内存块时,会发生这种情况,元素可以重新定位到的位置)。当向量尝试从先前的存储中重新定位元素时,您已经调用了 UB,因为它们所在的内存已经失效。
此外,通过不为您的分配器类型提供复制构造函数,编译器提供的实现执行浅拷贝(即,它复制指针,而不是存储在该地址下的数据),然后在析构函数。即调用:
v.get_allocator()
将创建分配器的浅表副本,创建分配器类型的纯右值,并在临时对象结束其生命周期后立即释放存储的指针(即,在包含 [=11 的完整语句结束时) =] 调用),导致在同一指针上对 alignedFree
进行两次调用。
我已经实现了一个 C++11 兼容的线性或竞技场分配器。代码如下。
linear_allocator.hpp:
#pragma once
#include <cstddef>
#include <cassert>
#include <new>
#include "aligned_mallocations.hpp"
template <typename T>
class LinearAllocator
{
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const 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(std::size_t count = 64)
: m_memUsed(0),
m_memStartAddress(nullptr)
{
allocate(count);
}
~LinearAllocator()
{
clear();
}
template <class U>
LinearAllocator(const LinearAllocator<U>&) noexcept
{}
/// \brief allocates memory equal to # count objects of type T
pointer allocate(std::size_t count)
{
if (count > std::size_t(-1) / sizeof(T))
{
throw std::bad_alloc{};
}
if (m_memStartAddress != nullptr)
{
alignedFree(m_memStartAddress);
}
m_memUsed = count * sizeof(T);
m_memStartAddress = static_cast<pointer>(alignedMalloc(m_memUsed, alignof(T)));
return m_memStartAddress;
}
/// \brief deallocates previously allocated memory
/// \brief Linear/arena allocators do not support free() operations. Use clear() instead.
void deallocate([[maybe_unused]] pointer p, [[maybe_unused]] std::size_t count) noexcept
{
//assert(false);
clear();
}
/// \brief simply resets memory
void clear()
{
if (m_memStartAddress != nullptr)
{
alignedFree(m_memStartAddress);
m_memStartAddress = nullptr;
}
this->m_memUsed = 0;
}
/// \brief GETTERS
pointer getStartAddress() const
{
return this->m_memStartAddress;
}
std::size_t getUsedMemory() const
{
return this->m_memUsed;
}
private:
std::size_t m_memUsed;
pointer m_memStartAddress;
};
template <class T, class U>
bool operator==(const LinearAllocator<T> &, const LinearAllocator<U> &)
{
return true;
}
template <class T, class U>
bool operator!=(const LinearAllocator<T> &, const LinearAllocator<U> &)
{
return false;
}
不用担心 alignedMalloc
和 alignedFree
。他们是正确的。
这是我的测试程序(linear_allocator.cpp):
#include "linear_allocator.hpp"
#include <vector>
#include <deque>
#include <iostream>
#include <string>
#include <typeinfo>
int main()
{
[[maybe_unused]]
LinearAllocator<int> a{1024};
std::cout << a.getStartAddress() << '\n';
std::cout << a.getUsedMemory() << '\n';
std::vector<std::string, LinearAllocator<std::string>> v;
v.reserve(100);
std::cout << "Vector capacity = " << v.capacity() << '\n';
//std::cout << v.get_allocator().getStartAddress() << '\n';
//std::cout << v.get_allocator().getUsedMemory() << '\n';
v.push_back("Hello");
v.push_back("w/e");
v.push_back("whatever");
v.push_back("there is ist sofi j");
v.push_back("wisdom");
v.push_back("fear");
v.push_back("there's more than meets the eye");
for (const auto &s : v)
{
std::cout << s << '\n';
}
std::cout << typeid(v.get_allocator()).name() << '\n';
std::deque<int, LinearAllocator<int>> dq;
dq.push_back(23);
dq.push_back(90);
dq.push_back(38794);
dq.push_back(7);
dq.push_back(0);
dq.push_back(2);
dq.push_back(13);
dq.push_back(24323);
dq.push_back(0);
dq.push_back(1234);
for (const auto &i : dq)
{
std::cout << i << '\n';
}
std::cout << typeid(dq.get_allocator()).name() << '\n';
}
使用 g++ -std=c++17 -O2 -march=native -Wall linear_allocator.cpp -o linear_allocator.gpp.exe
和 运行 linear_allocator.gpp.exe 编译得到输出:
0x4328b8
4096
Vector capacity = 100
Hello
w/e
whatever
there is ist sofi j
wisdom
fear
there's more than meets the eye
15LinearAllocatorINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE
如您所见,deque 的输出根本不存在。如果我取消注释这两行:
//std::cout << v.get_allocator().getStartAddress() << '\n';
//std::cout << v.get_allocator().getUsedMemory() << '\n';
矢量的输出也不会显示。
使用 MSVS cl 编译给出以下输出:
000000B47A1CAF88
4096
更糟糕的是
一定是我遗漏了什么,因为似乎有 UB,但我无法确定它在哪里。我的分配器设计基于 C++11+ 指南。我想知道我做错了什么。
虽然分配器负责提供和释放用于存储容器数据的内存,但它仍然只在容器请求时才这样做。也就是说,所提供存储的实际管理(特别是其生命周期)仍在容器方面。想象一下当向量执行其元素的重定位时会发生什么:
请求新的内存块,比当前(旧)内存块大一个给定因子。
存储在"old"块中的元素是copied/moved到新块。
才可以释放"old"内存块。
在您的实现中,一次只能有一个内存块处于活动状态——旧内存块在分配新内存块之前被释放(具体来说,当容器仅请求新内存块时,会发生这种情况,元素可以重新定位到的位置)。当向量尝试从先前的存储中重新定位元素时,您已经调用了 UB,因为它们所在的内存已经失效。
此外,通过不为您的分配器类型提供复制构造函数,编译器提供的实现执行浅拷贝(即,它复制指针,而不是存储在该地址下的数据),然后在析构函数。即调用:
v.get_allocator()
将创建分配器的浅表副本,创建分配器类型的纯右值,并在临时对象结束其生命周期后立即释放存储的指针(即,在包含 [=11 的完整语句结束时) =] 调用),导致在同一指针上对 alignedFree
进行两次调用。