在实践中曾经有个需求: 系统会陆续生产字节固定大小串, 检查是否重复,不重复就添加, 最大的数据量可达到万条记录,不使用磁盘,在内存中处理。
尝试了纯内存的sqlite, 占用内存太大了, 放弃;
也尝试直接使用std::map/std::set,都因为内存占用太大了,放弃;
最后是从std::map里把红黑树给抠出来, 使用整型及位域来减少节点变量大小, 一次性分配多个节点的内存之类的, 从而实现内存不超过230M, 满足了业务需求。
当时,如果是对std::allocator有所了解, 也许不用这么折腾了; 现在来学习学习。
1. 标准文档定义
std::allocator - cppreference.com
在标头 | |
template< class T > struct allocator; | |
template<> struct allocator | (C++ 中弃用) (C++ 中移除) |
如果不提供用户指定的分配器,那么 std::allocator 类模板是所有标准库容器使用的默认分配器。 默认分配器无状态,即给定分配器的任何实例都可交换、比较相等,且能由同一分配器类型的任何其他实例释放分配的内存。
对 void 的显式特化缺少成员类型定义(typedef)reference、const_reference、size_type 和 difference_type。 此特化不声明成员函数。 | (C++ 前) |
默认分配器满足分配器完整性要求。 | (C++ 起) |
成员类型
类型 | 定义 |
value_type | T |
pointer (C++ 中弃用) (C++ 中移除) | T* |
const_pointer (C++ 中弃用) (C++ 中移除) | const T* |
reference (C++ 中弃用) (C++ 中移除) | T& |
const_reference (C++ 中弃用) (C++ 中移除) | const T& |
size_type | std::size_t |
difference_type | std::ptrdiff_t |
propagate_on _container _move_assignment (C++) | std::true_type |
rebind (C++ 中弃用) (C++ 中移除) | template< class U > struct rebind { typedef allocator other; }; |
is_always_equal (C++) (C++ 中弃用) (C++ 中移除) | std::true_type |
成员函数
(构造函数) | 创建新的分配器实例 (公开成员函数) |
(析构函数) | 析构分配器实例 (公开成员函数) |
address(C++ 前) | 获得对象的地址,即使重载了 operator& (公开成员函数) |
allocate | 分配未初始化的存储 (公开成员函数) |
allocate_at_least(C++) | 分配与请求的大小至少一样大的未初始化存储 (公开成员函数) |
deallocate | 解分配存储 (公开成员函数) |
max_size(C++ 前) | 返回最大的受支持分配大小 (公开成员函数) |
construct(C++ 前) | 在分配的存储中构造对象 (公开成员函数) |
destroy(C++ 前) | 析构已分配存储中的对象 (公开成员函数) |
非成员函数
operator== operator!=(C++ 中移除) | 比较两个分配器实例 (公开成员函数) |
注解
成员模板 rebind 提供获得不同类型的分配器的方式。例如,std::list
成员类型 is_always_equal 由 LWG 问题 弃用,因为它使得派生自 std::allocator 的定制分配器默认被当作始终相等。
std::allocator_traitsstd::allocator
示例
#include
#include
#include
int main()
{
// int 的默认分配器
std::allocator alloc1;
// 演示少见的直接使用成员
static_assert(std::is_same_v);
int* p1 = alloc1.allocate(1); // 一个 int 的空间
alloc1.deallocate(p1, 1); // 而它没了
// 这些都可以通过特征使用,所以不需要直接使用
using traits_t1 = std::allocator_traits; // 匹配的特征
p1 = traits_t1::allocate(alloc1, 1);
traits_t1::construct(alloc1, p1, 7); // 构造 int
std::cout << *p1 << '\n';
traits_t1::deallocate(alloc1, p1, 1); // 解分配 int 的空间
// string 的默认分配器
std::allocator alloc2;
// 匹配的特征
using traits_t2 = std::allocator_traits;
// 用 string 的特征重绑定产生同一类型
traits_t2::rebind_alloc alloc_ = alloc2;
std::string* p2 = traits_t2::allocate(alloc2, 2); // 2 个 string 的空间
traits_t2::construct(alloc2, p2, "foo");
traits_t2::construct(alloc2, p2 + 1, "bar");
std::cout << p2[0] << ' ' << p2[1] << '\n';
traits_t2::destroy(alloc2, p2 + 1);
traits_t2::destroy(alloc2, p2);
traits_t2::deallocate(alloc2, p2, 2);
}
输出:
7
foo bar
构造函数
allocator() throw(); | (C++ 前) |
allocator() noexcept; | (C++ 起) (C++ 前) |
constexpr allocator() noexcept; | (C++ 起) |
allocator( const allocator& other ) throw(); | (C++ 前) |
allocator( const allocator& other ) noexcept; | (C++ 起) (C++ 前) |
constexpr allocator( const allocator& other ) noexcept; | (C++ 起) |
template< class U > allocator( const allocator& other ) throw(); | (C++ 前) |
template< class U > allocator( const allocator& other ) noexcept; | (C++ 起) (C++ 前) |
template< class U > constexpr allocator( const allocator& other ) noexcept; | (C++ 起) |
构造默认分配器。因为默认分配器是无状态的,故构造函数无可见效应。
参数
other | 用以构造的另一 allocator |
2. 探索
自定义allocator 要求(c++起, c++前)
- 构造函数(参考上面的构造函数定义)
allocator() noexcept;
allocator( const allocator& other ) noexcept;
template< class U > allocator( const allocator& other ) noexcept;
- 定义的成员类型(参考上面的成员类型)
value_type T
size_type std::size_t
difference_type std::ptrdiff_t
- 定义成员函数(参考上面的成员类型)
allocate
deallocate
address(非必要)
max_size(非必要)
construct(非必要)
destroy(非必要)
自定义allocator 示例
- 内部使用std::allocator方式
#include
#include
template
class Allocator1 {
public:
//成员类型
using value_type = _Ty;
//using size_type = size_t;
//using difference_type = ptrdiff_t;
//构造函数
constexpr Allocator1() noexcept {}
constexpr Allocator1(const Allocator1&) noexcept = default;
template
constexpr Allocator1(const Allocator1<_Other>&) noexcept {}
//成员函数
_Ty* allocate(const size_t _Count) {
return alloc.allocate(_Count);
}
void deallocate(_Ty* const _Ptr, const size_t _Count) {
return alloc.deallocate(_Ptr, _Count);
}
#if 0
//非必要实现
template
void construct(_Objty* const _Ptr, _Types&&... _Args) {
return alloc.construct<_Objty, _Types ...>(_Ptr, std::forward<_Types>(_Args) ... );
}
template
void destroy(_Uty* const _Ptr) {
alloc.destroy<_Uty>(_Ptr);
}
size_t max_size() const noexcept {
return alloc.max_size();
}
template
struct rebind {
using other = Allocator1<_Other>;
};
_Ty* address(_Ty& _Val) const noexcept {
return alloc.address(_Val);
}
const _Ty* address(const _Ty& _Val) const noexcept {
return alloc.address(_Val);
}
#endif
private:
std::allocator<_Ty> alloc;
};
int main() {
class A {
int val = 0;
public:
A(int x) : val(x){
std::cout << "A(" << val << ")" << std::endl;
}
~A() {
std::cout << "~A(" << val << ")" << std::endl;
}
};
std::vector> vec;
for (int i = 0; i < ; i++) {
vec.emplace_back(i);
}
auto alloc = vec.get_allocator();
auto x = alloc.allocate(1);
//alloc.construct(x, );
new (x) A);
x->~A();
alloc.deallocate(x, 1);
return 0;
}
- 继承 std::allocator 方式
#include
#include
#include
- 不使用std::allocator方式
#include
#include
#include
3. 总结
通过上面的学习, 对allocator有了一定的了解, 以后碰上了, 不至于一头雾水。
有兴趣可以测试下 std::unordered_map 与 std::map 空间占比。