目录
- 1.std::allocator
- 1.1C++中的placement new 和operator new
- 1.2一个custom allocator的实现
- 1.3使用std::allocator_traits实现allocator
1.std::allocator
C++中的std::allocator默默工作在C++STL中的所有容器的内存分配上,很多内存池是按照std::allocator的标准来实现的,甚至很多开源的内存储项目可以和大多数STL容器兼容,在很多场景下,内存池是std::allocator的优化。
在C++中,传统new操作符将内存分配(operator new,这里的operator new是C++的内存分配原语,默认调用C语言中的malloc,只是进行内存分配)和对象构造(构造函数)耦合。即new运算符需要同时完成内存分配和对象构造两个操作。
std::allocator将解耦内存分配和对象构造这两个操作,按照C++11的标准,实现一个std::allocator需要包含以下的元素和方法:
value_type:将模板的参数类型T定义为value_type,如using value_type = T;或者typedef T value_type;allocate():仅分配原始内存,功能就类似opeartor newconstruct():在预分配的内存上构造对象(通过使用C++中的placement new机制)destroy():析构对象但不释放内存deallocate():释放原始内存(类似于operator delete)
注释: https://cplusplus.com/reference/memory/allocator/
1.1C++中的placement new 和operator new
placement new 是C++中一种特使的内存分配的对象构造机制,它允许在已分配的内存上直接构造对象,而不是通过传统的new操作符同时分配内存和构造对象。
placement new的语法形式为:
new (pointer) Type(constructor_arguments);
其中:
pointer是指向已分配内存的指针Type是要构造的对象constructor_arguments是构造函数的参数
placement new的工作原理是,不调用operator new来分配内存,而是在给定的内存地址上直接调用构造函数www.devze.com,最后返回传入的指针(将指针类型转换为目标类型)。placement new由C++标准库提供默认实现,不可重载:
// 标准库中的 placement new 声明(不可重载)
void* operator new(size_t, void* ptr) noexcept {
return ptr; // 直接返回传入的指针
}
乍一看,这个placement new的实现什么都没干,是如何完成对象的构造呢?其实是依靠语法来进行创建的:
new (pointer) Type(constructor_arguments);
这里仍然调用了Type(constructor_arguments),即调用了对象的构造函数,在pointer指定的内存上进行构造,举个例子:
#include <IOStream>
struct Example {
int value;
Example(int val) : value(val) {
std::cout << "Constructed at " << this << " with value " << value << std::endl;
}
~Example() {
std::cout << "Destructed at " << this << std::endl;
}
};
int main() {
// 手动分配一块内存
void* buffer = operator new(sizeof(Example));
// 使用placement new在这块内存上构造对象
Example* obj = new (buffer) Example(42);
// 显式调用析构函数(这很重要!)
obj->~Example();
//js 释放内存
operator delete(buffer);
return 0;
}
输出为:
Constructed at 0x7fec4c400030 with value 42
Destructed at 0x7fec4c400030
operator new是C++的内存分配原语,默认调用malloc进行内存分配,返回void*,指向未初始化的原始内存,可以重载operator new以自定义其内存分配行为:
// 自定义全局 operator new
void* operator new(size_t size){
std::cout << "Allocating " << size << " bytes\n";
return malloc(size);
}
使用opeartor new和placement new的典型场景如下:
// 仅分配内存,不构造对象 void* raw_mem = operator new(sizeof(MyClass)); // 需要手动构造对象(例如通过 placement new) MyClass* obj = new (raw_mem) MyClass(); // 调用构造函数 // 必须手动析构和释放 obj->~MyClass(); operator delete(raw_mem);
核心区别:
| 特性 | operator new | placement new |
|---|---|---|
| 作用 | 仅分配原始内存(不构造对象) | 在已分配的内存上构造对象 |
| 是否调用构造函数 | 否 | 是 |
| 内存来源 | 通常来自于堆(可通过重载自定义) | 由程序员预先提供 |
| 语法 | void* p = operator new(size) | new (ptr) Type(args...) |
| 是否可重载 | 可重载全局或类特定的operator new | 不能重载,已经有固定实现 |
1.2一个custom allocator的实现
一个自定义的allocator需要实现以下的方法:
| 方法 | 描述 | 等效操作 |
|---|---|---|
| allocate(n) | 分配n* sizeof(T)字节 | operator new |
| deallocate(p, n) | 释放从p开始的n个元素 | operator delete |
| construct(p, args) | 在p构造对象(C++17已弃用) | new(p) T(args...) |
| destroy(p) | 析构p处对象(C++17已弃用) | p->~T() |
注释:C++17 后推荐通过 std::allocator_traits 访问接口,以支持自定义分配器的可选方法。
按照C++11的标准实现一个allocator:
#include <iostream>
#include <vector>
template<typename T>
class TrackingAllocator {
public:
using value_type = T;
TrackingAllocator() = default;
// 支持 Rebinding(重新绑定)
template<typename U>
TrackingAllocator(const TrackingAllocator<U>&) {}
T* allocate(size_t n) {
size_t bytes = n * sizeof(T);
std::cout << "Allocating " << bytes << " bytes\n";
return static_cast<T*>(::operator new(bytes));
}
void deallocate(T* p, size_t n) {
::operator delete(p);
std::cout << "Deallocating " << n * sizeof(T) << " bytes\n";
}
// 支持同类型分配器比较(无状态)
bool operator==(const TrackingAllocator&) { return true; }
bool operator!=(const TrackingAllocator&) { return false; }
};
// 使用示例
int main() {
// 使用自定义分配器
std::vector<int, TrackingAllocator<int>> vec;
vec.push_back(42); // 输出分配信息
vec.push_back(13); // 输出分配信息
// 清空向量
vec.clear(); // 输出释放信息
return 0;
}
输出:
Allocating 4 bytes
Allocating 8 bytesDeallocating 4 bytesDeallocating 8 bytes
1.3使用std::allocator_traits实现allocator
在 C++17 及之后版本中,推荐通过 std::allocator_traits 访问分配器接口,而非直接调用分配器的方法。这是因为 allocator_traits 提供了一种统一且安全的方式来与分配器交互,即使自定义分配器没有实现某些可选方法,也能通过默认实现正常工作。
- 兼容性:即使自定义分配器未实现某些方法(如
construct/destroy),allocator_traits会提供默认实现。 - 灵活性:允许分配器仅实现必要的接口,其余由
allocator_traits补充。 - 标准化:所有标准库容器(如
std::vector、std::list)内部都使用allocator_traits而非直接调用分配器。
注释:https://cplusplus.com/reference/memory/allocator_traits/
关键接口对比(使用C++11标准 vs. C++17标准)
| 操作 | C++11,直接调用分配器alloc | C++17,通过allocator_traits(std::allocator_traits<Alloc>) |
|---|---|---|
| 分配内存 | alloc.allocate(n) | allocator_traits<Alloc>::allocate(alloc, n) |
| 释放内存 | alloc.deallocate(p, n) | allocator_traits<Alloc>::deallocate(alloc, p, n编程客栈) |
| 构造对象 | alloc.construct(p, args) | allocator_traits<Alloc>::construct(alloc, p, args...) |
| 析构对象 | alloc.destroy(p) | allocator_traits<Alloc>::destroy(alloc, p) |
| 获取最大大小 | alloc.max_size() | allocator_traits<Alloc>::max_size(alloc) |
| 重新绑定分配器类型 | alloc.rebind<U>::other | allocator_traits<Alloc>::rebind_alloc<U> |
注释:C++17 后 construct 和 destroy 被废弃,推荐直接使用 std::allocator_traits 或 placement new/显式析构。
举个极简分配器的例子:
#include <iostream>
#include <memory> // std::allocator_traits
template <typename T>
struct SimpleAllocator {
using value_type = T;
// 必须提供 allocate 和 deallocate
T* allocate(size_t n) {
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* p, size_t n) {
::operator delete(p);
}
// 不提供 construct/destroy,由 allocator_traits 提供默www.devze.com认实现
};
struct Widget {
int id;
Widget(int i) : id(i) { std::cout << "Construct Widget " << id << "\n"; }
~Widget() { std::cout << "Destroy Widget " << id << "\n"; }
};
int main() {
using Alloc = SimpleAllocator<Widget>;
Alloc alloc;
// 1. 分配内存(通过 allocator_traits)
auto p = std::allocator_traits<Alloc>::allocate(alloc, 1);
// 2. 构造对象(即使 SimpleAllocator 没有 construct 方法!)
std::allocator_traits<Alloc>::construct(alloc, p, 42); // 调用 Widget(42)
// 3. 析构对象(即使 SimpleAllocator 没有 destroy 方法!)
std::allocator_traits<Alloc>::destroy(alloc, p);
// 4. 释放内存
std::allocator_traits<Alloc>::deallocate(alloc, p, 1);
return 0;
}
输出:
Construct Widget 42
Destroy Widget 42
一个更复杂的自定义分配器示例(带状态)
#include <iostream>
#include <memory> // std::allocator_traits
template <typename T>
class TrackingAllocator {
size_t total_allocated = 0;
public:
using value_type = T;
T* allocate(size_t n) {
total_allocated += n * sizeof(T);
std::cout << "Allocated " << n * sizeof(T) << " bytes (Total: " << total_allocated << ")\n";
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* p, size_t n) {
total_allocated -= n * sizeof(T);
std::cout << "Deallocated " << n * sizeof(T) << " bytes (Remaining: " << total_allocated << ")\n";
::operator delete(p);
}
// 支持比较(相同类型的 TrackingAllocator 才等价)
bool operator==(const TrackingAllocator& other) const {
return false; // 有状态,不同实例不能混用
}
bool operator!=(const TrackingAllocator& other) const {
return true;
}
};
int main() {
using Alloc = TrackingAllocator<int>;
Alloc alloc1, alloc2;
auto p1 = std::www.devze.comallocator_traits<Alloc>::allocate(alloc1, 2);
auto p2 = std::allocator_traits<Alloc>::allocate(alloc2, 3);
// 必须用相同的 allocator 实例释放!
std::allocator_traits<Alloc>::deallocate(alloc1, p1, 2);
std::allocator_traits<Alloc>::deallocate(alloc2, p2, 3);
return 0;
}
输出:
Allocated 8 bytes (Total: 8)
Allocated 12 bytes (Total: 12)Deallocated 8 bytes (Remaining: 0)Deallocated 12 bytes (Remaining: 0)
到此这篇关于C++中std::allocator的具体使用的文章就介绍到这了,更多相关C++ std::allocator内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
加载中,请稍侯......
精彩评论