Step 02
接入 Allocator
把本页「可粘贴代码」整段(从 template 到函数结尾)替换作业纸里对应的 TODO。不要贴进 class 体内。本步搜索 TODO(step 02),替换: allocate_n、deallocate_n、max_size。参考答案在 参考答案页;编译问题见 常见问题。
Step 02 — 接入 Allocator
不要直接 new T[n] / delete[]。原因有三:
new T[n]会构造 n 个 T,立刻要求默认构造函数。- 自定义分配器(池、统计、arena)插不进去。
vector需要的是“原始内存”,对象生命周期要自己管。
正确的入口是 std::allocator_traits<Allocator>:
| 你想做的事 | 调用 |
|---|---|
申请 n 个槽位的原始内存 |
AllocTraits::allocate(alloc_, n) |
还回去(n 必须和申请时一致) |
AllocTraits::deallocate(alloc_, p, n) |
| 在某个槽位上构造 T | AllocTraits::construct(alloc_, p, args...) |
| 析构那个槽位上的 T | AllocTraits::destroy(alloc_, p) |
| 这个分配器最多能给多少个 T | AllocTraits::max_size(alloc_) |
std::allocator<T> 只是默认值。用户传入的 CountingAllocator 只要提供 allocate / deallocate(以及可选的 construct / destroy),traits 会补齐其余接口。
空分配器不要占空间
std::allocator 是空类。C++20 的 [[no_unique_address]] 让它和 data_ 重叠,避免 Vector<int> 无故比三个指针/整数更大。作业纸里已经写了:
[[no_unique_address]] Allocator alloc_{};get_allocator() 也已经实现,不用改。
在作业纸里改哪里
全文搜索 TODO(step 02),一共三处。用下面三段分别整段替换对应的类外定义(从 template <typename T, typename Allocator> 到函数结束的 })。
不要把代码贴进 class 里面。
可粘贴代码
template <typename T, typename Allocator>
T* Vector<T, Allocator>::allocate_n(size_type n) {
if (n == 0) {
return nullptr;
}
return std::to_address(AllocTraits::allocate(alloc_, n));
}template <typename T, typename Allocator>
void Vector<T, Allocator>::deallocate_n(T* ptr, size_type n) noexcept {
if (ptr == nullptr) {
return;
}
AllocTraits::deallocate(alloc_, ptr, n);
}template <typename T, typename Allocator>
typename Vector<T, Allocator>::size_type
Vector<T, Allocator>::max_size() const noexcept {
const auto alloc_max = AllocTraits::max_size(alloc_);
const auto diff_max =
static_cast<size_type>(std::numeric_limits<difference_type>::max());
return alloc_max < diff_max ? alloc_max : diff_max;
}注意:
n == 0不要 allocate。有的分配器对 0 不友好;空 vector 保持data_ == nullptr也更简单。deallocate(nullptr)不要调用。标准只保证你还回allocate得到的指针。std::to_address把可能的 fancy pointer 转成T*,后面全部按裸指针算术处理。max_size取分配器上限和ptrdiff_t能表示的最大值里较小的那个,避免end() - begin()溢出。
allocate 只给你未初始化的 sizeof(T) * n 字节。此时对 ptr[i] 做 T 的成员访问是未定义行为。下一步才在上面 construct。
验收
这三步本身还没有独立测试会去调 allocate_n(那要等 reserve)。抄完先确认能编译:
cmake --build build自定义分配器测试在全抄完后跑:
./build/vector_tests --gtest_filter='CustomAllocator*'本步验收
cmake --build build
./build/vector_tests --gtest_filter='CustomAllocator*'