有哪些非常好的理由可以放弃使用 std::allocator
并选择自定义解决方案?您是否遇到过某些情况,其中正确性、性能、可伸缩性等是绝对必要的?有什么非常聪明的例子吗?
自定义分配器一直是标准库的一个特性,我并没有太多需求。我只是想知道这里的任何人能否提供一些令人信服的例子来证明它们的存在。
有哪些非常好的理由可以放弃使用 std::allocator
并选择自定义解决方案?您是否遇到过某些情况,其中正确性、性能、可伸缩性等是绝对必要的?有什么非常聪明的例子吗?
自定义分配器一直是标准库的一个特性,我并没有太多需求。我只是想知道这里的任何人能否提供一些令人信服的例子来证明它们的存在。
自定义分配器可以在游戏开发中特别有用,尤其是在游戏机上,因为它们只有很少的内存和没有交换空间。在这样的系统中,您希望确保对每个子系统都有严格的控制,以确保一个不重要的子系统不能从关键子系统中窃取内存。其他东西,如池分配器,可以帮助减少内存碎片化。您可以在以下网址找到有关该主题的详细论文:
#include <memory>
#include <stdio.h>
namespace mmap_allocator_namespace
{
// See StackOverflow replies to this answer for important commentary about inheriting from std::allocator before replicating this code.
template <typename T>
class mmap_allocator: public std::allocator<T>
{
public:
typedef size_t size_type;
typedef T* pointer;
typedef const T* const_pointer;
template<typename _Tp1>
struct rebind
{
typedef mmap_allocator<_Tp1> other;
};
pointer allocate(size_type n, const void *hint=0)
{
fprintf(stderr, "Alloc %d bytes.\n", n*sizeof(T));
return std::allocator<T>::allocate(n, hint);
}
void deallocate(pointer p, size_type n)
{
fprintf(stderr, "Dealloc %d bytes (%p).\n", n*sizeof(T), p);
return std::allocator<T>::deallocate(p, n);
}
mmap_allocator() throw(): std::allocator<T>() { fprintf(stderr, "Hello allocator!\n"); }
mmap_allocator(const mmap_allocator &a) throw(): std::allocator<T>(a) { }
template <class U>
mmap_allocator(const mmap_allocator<U> &a) throw(): std::allocator<T>(a) { }
~mmap_allocator() throw() { }
};
}
using namespace std;
using namespace mmap_allocator_namespace;
vector<int, mmap_allocator<int> > int_vec(1024, 0, mmap_allocator<int>());
它可以被用来记录内存分配的情况。必须有重新绑定结构体,否则向量容器会使用超类的allocate/deallocate方法。
更新:内存映射分配器现在可以在https://github.com/johannesthoma/mmap_allocator中找到,并且是LGPL许可证。欢迎在您的项目中使用它。
我正在使用一种使用C ++编写代码的MySQL存储引擎。我们使用自定义分配器来使用MySQL内存系统而不是与MySQL竞争内存。这使我们可以确保我们正在使用用户配置MySQL要使用的内存,而不是“额外”的内存。
使用自定义分配器以使用内存池而非堆可能是有用的。这只是众多示例之一。
对于大多数情况,这确实是一种过早优化。但在某些上下文中(嵌入式设备、游戏等),它可以非常有用。
在使用GPU或其他协处理器时,有时以一种特殊的方式分配主存中的数据结构会更加优势。可以通过自定义分配器方便地实现这种内存的特殊分配方式。
使用加速器时通过自定义分配器进行内存分配之所以可行是因为:
我在这里使用自定义分配器;你甚至可以说它是为了解决其他自定义动态内存管理而存在的。
背景:我们有malloc、calloc、free以及各种operator new和delete的重载版本,链接器很高兴地让STL使用这些。这使我们能够做到自动小对象池、泄漏检测、alloc fill、free fill、带哨兵的填充分配、某些allocs的缓存行对齐以及延迟释放等操作。
问题是,我们在嵌入式环境中运行--没有足够的内存来正确地进行泄漏检测会计工作。至少,在标准RAM中不行--还有另一个通过自定义分配函数可用的RAM堆。
解决方案:编写一个使用扩展堆的自定义分配器,并仅在内存泄漏跟踪架构的内部使用它... 其他所有内容都默认使用执行泄漏跟踪的普通new/delete重载。这避免了跟踪器跟踪自身(并提供了一些额外的打包功能,我们知道跟踪器节点的大小)。
我们还使用它来保留函数成本分析数据,出于同样的原因;为每个函数调用和返回以及线程切换编写一个条目可能会很快变得昂贵。自定义分配器再次为我们提供了较小的allocs,位于更大的调试内存区域中。
我正在使用自定义分配器来计算程序中某一部分的分配和释放次数,并测量所需时间。虽然还有其他方式可以实现这一目标,但这种方法对我非常方便。特别有用的是,我可以仅将自定义分配器用于我的容器的子集。
自定义分配器是一种安全地擦除内存的合理方式,然后再将其解除分配。
template <class T>
class allocator
{
public:
using value_type = T;
allocator() noexcept {}
template <class U> allocator(allocator<U> const&) noexcept {}
value_type* // Use pointer if pointer is not a value_type*
allocate(std::size_t n)
{
return static_cast<value_type*>(::operator new (n*sizeof(value_type)));
}
void
deallocate(value_type* p, std::size_t) noexcept // Use pointer if pointer is not a value_type*
{
OPENSSL_cleanse(p, n);
::operator delete(p);
}
};
template <class T, class U>
bool
operator==(allocator<T> const&, allocator<U> const&) noexcept
{
return true;
}
template <class T, class U>
bool
operator!=(allocator<T> const& x, allocator<U> const& y) noexcept
{
return !(x == y);
}
建议使用 Hinnant 的分配器样板代码: https://howardhinnant.github.io/allocator_boilerplate.html)
boost::pool
和boost::interprocess
- Mooing Duck