Step 02

接入 Allocator

粘贴到作业纸 student/mini_vector/vector.hpp 底部不需要仓库,起步页已有全部工程文件

把本页「可粘贴代码」整段(从 template 到函数结尾)替换作业纸里对应的 TODO。不要贴进 class 体内。本步搜索 TODO(step 02),替换: allocate_n、deallocate_n、max_size。参考答案在 参考答案页;编译问题见 常见问题。

Step 02 — 接入 Allocator

不要直接 new T[n] / delete[]。原因有三:

  1. new T[n] 会构造 n 个 T,立刻要求默认构造函数。
  2. 自定义分配器(池、统计、arena)插不进去。
  3. 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*'