基于堆栈缓冲区的STL分配器? 我想知道是否有一个C++标准库兼容的分配器< /> >,它使用一个(固定大小)的缓冲区,它存在于堆栈上。
不知何故,这个问题似乎还没有被这样问过,尽管它可能在别处得到了含蓄的回答 因此,就我的搜索而言,基本上应该可以创建一个使用固定大小缓冲区的分配器。现在,乍一看,这意味着还可以有一个使用固定大小缓冲区的分配器,该缓冲区“存在”在堆栈上,但似乎没有广泛的这种实现 让我举一个例子来说明我的意思:基于堆栈缓冲区的STL分配器? 我想知道是否有一个C++标准库兼容的分配器< /> >,它使用一个(固定大小)的缓冲区,它存在于堆栈上。,c++,stl,stack,allocator,C++,Stl,Stack,Allocator,不知何故,这个问题似乎还没有被这样问过,尽管它可能在别处得到了含蓄的回答 因此,就我的搜索而言,基本上应该可以创建一个使用固定大小缓冲区的分配器。现在,乍一看,这意味着还可以有一个使用固定大小缓冲区的分配器,该缓冲区“存在”在堆栈上,但似乎没有广泛的这种实现 让我举一个例子来说明我的意思: { ... char buf[512]; typedef ...hmm?... local_allocator; // should use buf typedef std::basic_stri
{ ...
char buf[512];
typedef ...hmm?... local_allocator; // should use buf
typedef std::basic_string<char, std::char_traits<char>, local_allocator> lstring;
lstring str; // string object of max. 512 char
}
{。。。
char-buf[512];
typedef…hmm?…本地分配程序;//应使用buf
typedef std::基本字符串lstring;
lstring str;//最多512个字符的字符串对象
}
这将如何实现
(感谢R.Martinho Fernandes)链接到chromium源中基于堆栈的分配器:
然而,这个类看起来非常奇怪,特别是因为这个
堆栈分配器没有默认的构造函数,我在想。它实际上取决于您的需求,当然,如果您愿意,您可以创建一个仅在堆栈上操作的分配器,但它将非常有限,因为在程序中,不能像堆对象那样从任何地方访问同一个堆栈对象
我认为这篇文章很好地解释了它
基于堆栈的STL分配器的实用性非常有限,我怀疑您会发现很多现有技术。如果您以后决定复制或延长初始的lstring
,即使您引用的简单示例也会很快崩溃
对于其他STL容器,例如使用单个或多个连续RAM块的关联容器(内部基于树)或甚至vector
和deque
,内存使用语义在几乎任何实际使用中都会很快变得难以管理。这实际上是一种非常有用的实践,在性能开发(如游戏)中经常使用。将内存内嵌到堆栈上或类结构的分配中,对于容器的速度和/或管理来说是至关重要的
要回答您的问题,可以归结为stl容器的实现。如果容器不仅实例化,而且作为成员保留对您的分配器的引用,那么您最好创建一个固定堆,我发现情况并非总是如此,因为它不属于规范的一部分。否则,它就会出现问题。一种解决方案是用另一个包含存储的类包装容器、向量、列表等。然后您可以使用分配器从中提取。这可能需要大量的模板magickery(tm) 显然,有一个
它的工作原理是使用固定大小的缓冲区(通过引用的arena
对象),如果请求的空间太大,则返回堆
这个分配器没有默认的ctor,因为Howard说:
我用一个完全符合C++11的新分配器更新了本文
我要说的是,分配器不需要有默认的ctor。创建完全符合C++11/C++14的堆栈分配器是绝对可能的*。但是,您需要考虑一些有关堆栈分配的实现和语义以及它们如何与标准容器交互的后果。
这是一个完全符合C++11/C++14标准的堆栈分配器(也托管在我的主机上):
见:
同样,这对于vector也很好——但您需要问问自己,您到底打算用堆栈分配器做什么。如果您想要一个恰好从堆栈缓冲区提取的通用内存分配器,那么您所说的是一个更复杂的项目。然而,一个简单的堆栈分配器(它只增加和减少堆栈指针)将适用于有限的用例集。请注意,对于非POD类型,您需要使用std::aligned_storage
来创建实际的堆栈缓冲区
我还要注意的是,与此不同,上面的实现没有显式地检查在调用deallocate()
时,传入的指针是否是最后分配的块。如果传入的指针不是LIFO顺序的释放,Hinnant的实现将什么也不做。这将使您能够使用std::vector
,而无需事先保留,因为分配器基本上会忽略该向量尝试取消分配初始缓冲区的操作。但这也使分配器的语义模糊了一点,并且依赖于与已知的std::vector
工作方式非常明确的行为。我的感觉是,我们不妨简单地说,将未通过上次调用返回的任何指向deallocate()
的指针传递给allocate()
,将导致未定义的行为,并将其保留
*最后-以下警告:检查指针是否在堆栈缓冲区边界内的函数似乎是由标准行为定义的。比较来自不同new
/malloc
'd缓冲区的两个指针的顺序可以说是实现定义的行为(即使使用std::less
),这可能会导致无法编写一个遵循标准的堆栈分配器实现,而该实现依赖于堆分配。(但实际上这并不重要,除非您在MS-DOS上运行80286。)
**最后(实际上是现在),还值得注意的是,堆栈分配器中的单词“stack”在某种程度上被重载,以引用内存源(固定大小的堆栈数组)和分配方法(后进先出递增/递减堆栈指针)。当大多数程序员说他们想要一个s
#include <functional>
#include <memory>
template <class T, std::size_t N, class Allocator = std::allocator<T>>
class stack_allocator
{
public:
typedef typename std::allocator_traits<Allocator>::value_type value_type;
typedef typename std::allocator_traits<Allocator>::pointer pointer;
typedef typename std::allocator_traits<Allocator>::const_pointer const_pointer;
typedef typename Allocator::reference reference;
typedef typename Allocator::const_reference const_reference;
typedef typename std::allocator_traits<Allocator>::size_type size_type;
typedef typename std::allocator_traits<Allocator>::difference_type difference_type;
typedef typename std::allocator_traits<Allocator>::const_void_pointer const_void_pointer;
typedef Allocator allocator_type;
public:
explicit stack_allocator(const allocator_type& alloc = allocator_type())
: m_allocator(alloc), m_begin(nullptr), m_end(nullptr), m_stack_pointer(nullptr)
{ }
explicit stack_allocator(pointer buffer, const allocator_type& alloc = allocator_type())
: m_allocator(alloc), m_begin(buffer), m_end(buffer + N),
m_stack_pointer(buffer)
{ }
template <class U>
stack_allocator(const stack_allocator<U, N, Allocator>& other)
: m_allocator(other.m_allocator), m_begin(other.m_begin), m_end(other.m_end),
m_stack_pointer(other.m_stack_pointer)
{ }
constexpr static size_type capacity()
{
return N;
}
pointer allocate(size_type n, const_void_pointer hint = const_void_pointer())
{
if (n <= size_type(std::distance(m_stack_pointer, m_end)))
{
pointer result = m_stack_pointer;
m_stack_pointer += n;
return result;
}
return m_allocator.allocate(n, hint);
}
void deallocate(pointer p, size_type n)
{
if (pointer_to_internal_buffer(p))
{
m_stack_pointer -= n;
}
else m_allocator.deallocate(p, n);
}
size_type max_size() const noexcept
{
return m_allocator.max_size();
}
template <class U, class... Args>
void construct(U* p, Args&&... args)
{
m_allocator.construct(p, std::forward<Args>(args)...);
}
template <class U>
void destroy(U* p)
{
m_allocator.destroy(p);
}
pointer address(reference x) const noexcept
{
if (pointer_to_internal_buffer(std::addressof(x)))
{
return std::addressof(x);
}
return m_allocator.address(x);
}
const_pointer address(const_reference x) const noexcept
{
if (pointer_to_internal_buffer(std::addressof(x)))
{
return std::addressof(x);
}
return m_allocator.address(x);
}
template <class U>
struct rebind { typedef stack_allocator<U, N, allocator_type> other; };
pointer buffer() const noexcept
{
return m_begin;
}
private:
bool pointer_to_internal_buffer(const_pointer p) const
{
return (!(std::less<const_pointer>()(p, m_begin)) && (std::less<const_pointer>()(p, m_end)));
}
allocator_type m_allocator;
pointer m_begin;
pointer m_end;
pointer m_stack_pointer;
};
template <class T1, std::size_t N, class Allocator, class T2>
bool operator == (const stack_allocator<T1, N, Allocator>& lhs,
const stack_allocator<T2, N, Allocator>& rhs) noexcept
{
return lhs.buffer() == rhs.buffer();
}
template <class T1, std::size_t N, class Allocator, class T2>
bool operator != (const stack_allocator<T1, N, Allocator>& lhs,
const stack_allocator<T2, N, Allocator>& rhs) noexcept
{
return !(lhs == rhs);
}
const static std::size_t stack_size = 4;
int buffer[stack_size];
typedef stack_allocator<int, stack_size> allocator_type;
std::vector<int, allocator_type> vec((allocator_type(buffer))); // double parenthesis here for "most vexing parse" nonsense
vec.reserve(stack_size); // attempt to reserve space for 4 elements
std::cout << vec.capacity() << std::endl;
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);
vec.push_back(40);
// Assert that the vector is actually using our stack
//
assert(
std::equal(
vec.begin(),
vec.end(),
buffer,
[](const int& v1, const int& v2) {
return &v1 == &v2;
}
)
);
// Output some values in the stack, we see it is the same values we
// inserted in our vector.
//
std::cout << buffer[0] << std::endl;
std::cout << buffer[1] << std::endl;
std::cout << buffer[2] << std::endl;
std::cout << buffer[3] << std::endl;
// Attempt to push back some more values. Since our stack allocator only has
// room for 4 elements, we cannot satisfy the request for an 8 element buffer.
// So, the allocator quietly falls back on using std::allocator.
//
// Alternatively, you could modify the stack_allocator implementation
// to throw std::bad_alloc
//
vec.push_back(50);
vec.push_back(60);
vec.push_back(70);
vec.push_back(80);
// Assert that we are no longer using the stack buffer
//
assert(
!std::equal(
vec.begin(),
vec.end(),
buffer,
[](const int& v1, const int& v2) {
return &v1 == &v2;
}
)
);
// Print out all the values in our vector just to make sure
// everything is sane.
//
for (auto v : vec) std::cout << v << ", ";
std::cout << std::endl;
template<std::size_t Size=256>
class bumping_memory_resource {
public:
char buffer[Size];
char* _ptr;
explicit bumping_memory_resource()
: _ptr(&buffer[0]) {}
void* allocate(std::size_t size) noexcept {
auto ret = _ptr;
_ptr += size;
return ret;
}
void deallocate(void*) noexcept {}
};
template <typename T, typename Resource=bumping_memory_resource<256>>
class bumping_allocator {
Resource* _res;
public:
using value_type = T;
explicit bumping_allocator(Resource& res)
: _res(&res) {}
bumping_allocator(const bumping_allocator&) = default;
template <typename U>
bumping_allocator(const bumping_allocator<U,Resource>& other)
: bumping_allocator(other.resource()) {}
Resource& resource() const { return *_res; }
T* allocate(std::size_t n) { return static_cast<T*>(_res->allocate(sizeof(T) * n)); }
void deallocate(T* ptr, std::size_t) { _res->deallocate(ptr); }
friend bool operator==(const bumping_allocator& lhs, const bumping_allocator& rhs) {
return lhs._res == rhs._res;
}
friend bool operator!=(const bumping_allocator& lhs, const bumping_allocator& rhs) {
return lhs._res != rhs._res;
}
};